import 'dart:async';

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logger/logger.dart';
import 'package:real_estate_mobile/features/auth/presentation/providers/auth_provider.dart';
import 'package:real_estate_mobile/features/messaging/data/models/messaging_models.dart';
import 'package:real_estate_mobile/features/messaging/data/messaging_repository.dart';
import 'package:real_estate_mobile/features/messaging/data/socket_service.dart';

final _log = Logger(printer: PrettyPrinter(methodCount: 0));

class MessagingState {
  final List<Conversation> conversations;
  final Map<String, List<ChatMessage>> messages;
  final Map<String, PaginationInfo> pagination;
  final Set<String> typingUsers;
  final bool isLoadingConversations;
  final bool isLoadingMessages;
  final bool isSending;
  final String? error;
  final String? activeConversationId;
  final int unreadCount;

  const MessagingState({
    this.conversations = const [],
    this.messages = const {},
    this.pagination = const {},
    this.typingUsers = const {},
    this.isLoadingConversations = false,
    this.isLoadingMessages = false,
    this.isSending = false,
    this.error,
    this.activeConversationId,
    this.unreadCount = 0,
  });

  MessagingState copyWith({
    List<Conversation>? conversations,
    Map<String, List<ChatMessage>>? messages,
    Map<String, PaginationInfo>? pagination,
    Set<String>? typingUsers,
    bool? isLoadingConversations,
    bool? isLoadingMessages,
    bool? isSending,
    String? error,
    String? activeConversationId,
    int? unreadCount,
    bool clearError = false,
    bool clearActiveConversation = false,
  }) {
    return MessagingState(
      conversations: conversations ?? this.conversations,
      messages: messages ?? this.messages,
      pagination: pagination ?? this.pagination,
      typingUsers: typingUsers ?? this.typingUsers,
      isLoadingConversations:
          isLoadingConversations ?? this.isLoadingConversations,
      isLoadingMessages: isLoadingMessages ?? this.isLoadingMessages,
      isSending: isSending ?? this.isSending,
      error: clearError ? null : (error ?? this.error),
      activeConversationId: clearActiveConversation
          ? null
          : (activeConversationId ?? this.activeConversationId),
      unreadCount: unreadCount ?? this.unreadCount,
    );
  }
}

class MessagingNotifier extends StateNotifier<MessagingState> {
  final MessagingRepository _repository;
  final Ref _ref;
  final SocketService _socket = SocketService();
  final List<StreamSubscription> _subscriptions = [];

  MessagingNotifier(this._repository, this._ref)
      : super(const MessagingState()) {
    _initSocketListeners();
  }

  String? get _currentUserId => _ref.read(authProvider).user?.id;

  void _initSocketListeners() {
    // Online/offline status changes
    _subscriptions.add(_socket.onStatusChange.listen((data) {
      final userId = data['userId'] as String?;
      final isOnline = data['isOnline'] as bool? ?? false;
      final lastSeenAt = data['lastSeenAt'] as String?;
      if (userId != null) {
        updateUserStatus(userId, isOnline, lastSeenAt: lastSeenAt);
      }
    }));

    // Incoming messages from other users
    _subscriptions.add(_socket.onNewMessage.listen((message) {
      if (!mounted) return;
      final convId = message.conversationId;
      final currentMessages =
          List<ChatMessage>.from(state.messages[convId] ?? []);

      // Avoid duplicates
      if (currentMessages.any((m) => m.id == message.id)) return;

      currentMessages.insert(0, message);
      final updatedMessages =
          Map<String, List<ChatMessage>>.from(state.messages)
            ..[convId] = currentMessages;

      // Update conversation's last message and unread count
      final updatedConversations = state.conversations.map((c) {
        if (c.id == convId) {
          final isActive = state.activeConversationId == convId;
          return c.copyWith(
            lastMessageText: message.content,
            lastMessageAt: message.createdAt,
            unreadCount: isActive ? c.unreadCount : c.unreadCount + 1,
          );
        }
        return c;
      }).toList();

      state = state.copyWith(
        messages: updatedMessages,
        conversations: updatedConversations,
      );
    }));

    // Typing indicators
    _subscriptions.add(_socket.onTypingStart.listen((data) {
      if (!mounted) return;
      final userId = data['userId'] as String?;
      if (userId != null && userId != _currentUserId) {
        state = state.copyWith(
          typingUsers: {...state.typingUsers, userId},
        );
      }
    }));

    _subscriptions.add(_socket.onTypingStop.listen((data) {
      if (!mounted) return;
      final userId = data['userId'] as String?;
      if (userId != null) {
        final updated = Set<String>.from(state.typingUsers)..remove(userId);
        state = state.copyWith(typingUsers: updated);
      }
    }));

    // Read receipts
    _subscriptions.add(_socket.onMessagesRead.listen((data) {
      if (!mounted) return;
      final convId = data['conversationId'] as String?;
      if (convId == null) return;
      final updatedConversations = state.conversations.map((c) {
        if (c.id == convId) return c.copyWith(unreadCount: 0);
        return c;
      }).toList();
      state = state.copyWith(conversations: updatedConversations);
    }));
  }

  /// Update online status for a user across all conversations (matching web)
  void updateUserStatus(String userId, bool isOnline, {String? lastSeenAt}) {
    if (!mounted) return;
    final updatedConversations = state.conversations.map((c) {
      if (c.otherParty.userId == userId || c.otherParty.id == userId) {
        return c.copyWith(
          otherParty: c.otherParty.copyWith(
            isOnline: isOnline,
            lastSeenAt: lastSeenAt,
          ),
        );
      }
      return c;
    }).toList();

    state = state.copyWith(conversations: updatedConversations);
  }

  @override
  void dispose() {
    for (final sub in _subscriptions) {
      sub.cancel();
    }
    _subscriptions.clear();
    super.dispose();
  }

  Future<void> loadConversations() async {
    state = state.copyWith(isLoadingConversations: true, clearError: true);
    try {
      final result = await _repository.getConversations();
      state = state.copyWith(
        conversations: result,
        isLoadingConversations: false,
      );
    } catch (e, stack) {
      _log.e('Failed to load conversations', error: e, stackTrace: stack);
      state = state.copyWith(
        isLoadingConversations: false,
        error: 'Failed to load conversations',
      );
    }
  }

  Future<void> loadMessages(
    String conversationId, {
    bool loadMore = false,
  }) async {
    if (loadMore) {
      final existing = state.pagination[conversationId];
      if (existing != null && !existing.hasMore) return;
    }

    state = state.copyWith(isLoadingMessages: true, clearError: true);

    try {
      final nextPage =
          loadMore ? ((state.pagination[conversationId]?.page ?? 0) + 1) : 1;

      final result = await _repository.getMessages(
        conversationId,
        page: nextPage,
      );

      // API returns chronological order (oldest first), but our ListView is
      // reverse: true so we need newest-first order (index 0 = newest).
      final newMessages = result.messages.reversed.toList();
      final paginationInfo = result.pagination;

      final existingMessages =
          loadMore ? (state.messages[conversationId] ?? []) : <ChatMessage>[];
      // When loading more (older messages), append to end (older = higher index)
      final mergedMessages =
          loadMore ? [...existingMessages, ...newMessages] : newMessages;

      final updatedMessages =
          Map<String, List<ChatMessage>>.from(state.messages)
            ..[conversationId] = mergedMessages;

      final updatedPagination =
          Map<String, PaginationInfo>.from(state.pagination)
            ..[conversationId] = paginationInfo;

      state = state.copyWith(
        messages: updatedMessages,
        pagination: updatedPagination,
        isLoadingMessages: false,
      );
    } catch (_) {
      state = state.copyWith(
        isLoadingMessages: false,
        error: 'Failed to load messages',
      );
    }
  }

  Future<void> sendMessage(String conversationId, String content) async {
    final currentUserId = _currentUserId;
    if (currentUserId == null) return;

    final tempId = 'temp_${DateTime.now().millisecondsSinceEpoch}';
    final now = DateTime.now().toIso8601String();

    final tempMessage = ChatMessage(
      id: tempId,
      conversationId: conversationId,
      senderId: currentUserId,
      content: content,
      messageType: MessageType.text,
      status: MessageStatus.sent,
      createdAt: now,
      updatedAt: now,
      sender: MessageSender(
        id: currentUserId,
        role: 'user',
      ),
    );

    // Add optimistic message at the beginning (newest-first)
    final currentMessages =
        List<ChatMessage>.from(state.messages[conversationId] ?? []);
    currentMessages.insert(0, tempMessage);

    final updatedMessages =
        Map<String, List<ChatMessage>>.from(state.messages)
          ..[conversationId] = currentMessages;

    state = state.copyWith(
      messages: updatedMessages,
      isSending: true,
      clearError: true,
    );

    try {
      final realMessage = await _repository.sendMessage(
        conversationId,
        content: content,
      );

      final messagesAfterSend =
          List<ChatMessage>.from(state.messages[conversationId] ?? []);
      final tempIndex = messagesAfterSend.indexWhere((m) => m.id == tempId);
      if (tempIndex != -1) {
        messagesAfterSend[tempIndex] = realMessage;
      }

      final finalMessages =
          Map<String, List<ChatMessage>>.from(state.messages)
            ..[conversationId] = messagesAfterSend;

      // Update conversation's last message
      final updatedConversations = state.conversations.map((c) {
        if (c.id == conversationId) {
          return c.copyWith(
            lastMessageText: realMessage.content,
            lastMessageAt: realMessage.createdAt,
          );
        }
        return c;
      }).toList();

      state = state.copyWith(
        messages: finalMessages,
        conversations: updatedConversations,
        isSending: false,
      );
    } catch (_) {
      final messagesAfterError =
          List<ChatMessage>.from(state.messages[conversationId] ?? []);
      messagesAfterError.removeWhere((m) => m.id == tempId);

      final errorMessages =
          Map<String, List<ChatMessage>>.from(state.messages)
            ..[conversationId] = messagesAfterError;

      state = state.copyWith(
        messages: errorMessages,
        isSending: false,
        error: 'Failed to send message',
      );
    }
  }

  Future<void> markAsRead(String conversationId) async {
    try {
      await _repository.markAsRead(conversationId);

      final updatedConversations = state.conversations.map((c) {
        if (c.id == conversationId) {
          return c.copyWith(unreadCount: 0);
        }
        return c;
      }).toList();

      final totalUnread = updatedConversations.fold<int>(
        0,
        (sum, c) => sum + c.unreadCount,
      );

      state = state.copyWith(
        conversations: updatedConversations,
        unreadCount: totalUnread,
      );
    } catch (_) {}
  }

  void setActiveConversation(String? conversationId) {
    if (conversationId == null) {
      state = state.copyWith(clearActiveConversation: true);
    } else {
      state = state.copyWith(activeConversationId: conversationId);
    }
  }

  Future<void> loadUnreadCount() async {
    try {
      final count = await _repository.getUnreadCount();
      state = state.copyWith(unreadCount: count);
    } catch (_) {}
  }

  Future<String?> startConversation(String agentProfileId) async {
    if (!mounted) return null;
    state = state.copyWith(isLoadingConversations: true, clearError: true);
    try {
      final conversation =
          await _repository.startConversation(agentProfileId);

      if (!mounted) return conversation.id;

      final exists = state.conversations.any((c) => c.id == conversation.id);
      final updatedConversations = exists
          ? state.conversations
          : [conversation, ...state.conversations];

      state = state.copyWith(
        conversations: updatedConversations,
        isLoadingConversations: false,
      );
      return conversation.id;
    } catch (_) {
      if (mounted) {
        state = state.copyWith(
          isLoadingConversations: false,
          error: 'Failed to start conversation',
        );
      }
      return null;
    }
  }

  Future<String?> startConversationWithUser(String userId) async {
    if (!mounted) return null;
    state = state.copyWith(isLoadingConversations: true, clearError: true);
    try {
      final conversation =
          await _repository.startConversationWithUser(userId);

      if (!mounted) return conversation.id;

      final exists = state.conversations.any((c) => c.id == conversation.id);
      final updatedConversations = exists
          ? state.conversations
          : [conversation, ...state.conversations];

      state = state.copyWith(
        conversations: updatedConversations,
        isLoadingConversations: false,
      );
      return conversation.id;
    } catch (_) {
      if (mounted) {
        state = state.copyWith(
          isLoadingConversations: false,
          error: 'Failed to start conversation',
        );
      }
      return null;
    }
  }
}

final messagingProvider =
    StateNotifierProvider<MessagingNotifier, MessagingState>((ref) {
  return MessagingNotifier(ref.watch(messagingRepositoryProvider), ref);
});

final currentUserIdProvider = Provider<String?>((ref) {
  return ref.watch(authProvider).user?.id;
});
