import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../domain/entities/chat.dart';
import '../../domain/entities/message.dart';
import '../../domain/repositories/chat_repository.dart';
import 'chat_event.dart';
import 'chat_state.dart';

class ChatBloc extends Bloc<ChatEvent, ChatState> {
  final ChatRepository chatRepository;
  StreamSubscription? _messageSubscription;
  StreamSubscription? _typingSubscription;
  StreamSubscription? _statusSubscription;
  StreamSubscription? _readReceiptSubscription;
  Timer? _refreshTimer;
  Timer? _retryTimer;
  String? _currentUserId;
  final Set<String> _sendingIds = {};

  ChatBloc({required this.chatRepository}) : super(const ChatState.initial()) {
    on<ChatEventStarted>(_onStarted);
    on<ChatEventChatsLoaded>(_onChatsLoaded);
    on<ChatEventChatSelected>(_onChatSelected);
    on<ChatEventMessagesRequested>(_onMessagesRequested);
    on<ChatEventMessageReceived>(_onMessageReceived);
    on<ChatEventMessageSent>(_onMessageSent);
    on<ChatEventFavoritesRequested>(_onFavoritesRequested);
    on<ChatEventTypingUpdated>(_onTypingUpdated);
    on<ChatEventUserStatusChanged>(_onUserStatusChanged);
    on<ChatEventRefreshPresence>(_onRefreshPresence);
    on<ChatEventSendTypingStatus>(_onSendTypingStatus);
    on<ChatEventMarkAsRead>(_onMarkAsRead);
    on<ChatEventReadReceiptReceived>(_onReadReceiptReceived);
    on<ChatEventMessageStatusUpdated>(_onMessageStatusUpdated);
    on<ChatEventMessageReplaced>(_onMessageReplaced);
    on<ChatEventCacheCleared>(_onCacheCleared);
    
    _messageSubscription = chatRepository.messageStream.listen((message) {
      add(ChatEvent.messageReceived(message));
    });

    _typingSubscription = chatRepository.typingStream.listen((data) {
      add(ChatEvent.typingUpdated(data['chatId'], data['userId'], data['isTyping']));
    });

    _statusSubscription = chatRepository.statusStream.listen((data) {
      add(ChatEvent.userStatusChanged(
        data['userId'], 
        data['isOnline'], 
        lastSeen: data['lastSeen'] != null ? DateTime.tryParse(data['lastSeen'].toString()) : null,
      ));
    });

    _readReceiptSubscription = chatRepository.readReceiptStream.listen((data) {
      add(ChatEvent.readReceiptReceived(
        chatId: data['chatId'],
        userId: data['userId'],
        lastReadMessageId: data['lastReadMessageId'],
        lastReadSequenceId: int.tryParse(data['lastReadSequenceId'].toString()) ?? 0,
      ));
    });

    // Refresh presence status every minute to update "X minutes ago"
    _refreshTimer = Timer.periodic(const Duration(minutes: 1), (_) {
      add(const ChatEvent.refreshPresence());
    });

    // Retry sending messages every 10 seconds
    _retryTimer = Timer.periodic(const Duration(seconds: 10), (_) {
      _retryPendingMessages();
    });
  }

  Future<void> _onStarted(ChatEventStarted event, Emitter<ChatState> emit) async {
    _currentUserId = event.userId;
    // ignore: avoid_print
    print('[DEBUG] ChatBloc._onStarted for user: ${event.userId}');
    // Start SignalR in background
    chatRepository.initSignalR().catchError((e) {
      // Log or handle SignalR init error
    });
    // Immediately trigger chats loading
    add(ChatEvent.chatsLoaded(currentUserId: event.userId));
  }

  Future<void> _onChatsLoaded(ChatEventChatsLoaded event, Emitter<ChatState> emit) async {
    emit(const ChatState.loading());
    final result = await chatRepository.getChats(currentUserId: event.currentUserId);
    result.fold(
      (error) => emit(ChatState.error(error.message ?? 'Failed to load chats')),
      (chats) => emit(ChatState.chatsLoaded(chats)),
    );
  }

  Future<void> _onChatSelected(ChatEventChatSelected event, Emitter<ChatState> emit) async {
    final currentChatId = state.maybeMap(
      chatSelected: (s) => s.chat.id,
      messagesLoaded: (s) => s.chat.id,
      orElse: () => null,
    );

    if (currentChatId == event.chatId) {
      // Already in this chat, just refresh messages
      final messagesResult = await chatRepository.getMessages(event.chatId);
      messagesResult.fold(
        (error) => null,
        (messages) {
          final currentChat = state.maybeMap(
            chatSelected: (s) => s.chat,
            messagesLoaded: (s) => s.chat,
            orElse: () => null,
          );
          if (currentChat != null) {
            emit(ChatState.messagesLoaded(currentChat, messages, typingUsers: state.typingUsers));
          }
        },
      );
      return;
    }

    emit(ChatState.loading(typingUsers: state.typingUsers));
    final chatResult = await chatRepository.getChatById(event.chatId);
    final messagesResult = await chatRepository.getMessages(event.chatId);
    
    chatResult.fold(
      (error) => emit(ChatState.error(error.message ?? 'Failed to load chat', typingUsers: state.typingUsers)),
      (chat) {
        messagesResult.fold(
          (error) => emit(ChatState.chatSelected(chat, [], typingUsers: state.typingUsers)),
          (messages) => emit(ChatState.chatSelected(chat, messages, typingUsers: state.typingUsers)),
        );
      },
    );
  }

  Future<void> _onMessagesRequested(ChatEventMessagesRequested event, Emitter<ChatState> emit) async {
    final currentChat = state.maybeMap(
      chatSelected: (s) => s.chat,
      messagesLoaded: (s) => s.chat,
      orElse: () => null,
    );

    // Only fetch if it's the current chat or we are in chats list
    final result = await chatRepository.getMessages(event.chatId);
    result.fold(
      (error) => null, // Don't emit error state for background updates to avoid UI flickering
      (messages) {
        if (currentChat != null && currentChat.id == event.chatId) {
          // If we are in the chat, update messages and switch to messagesLoaded (stable state)
          emit(ChatState.messagesLoaded(currentChat, messages, typingUsers: state.typingUsers));
        }
        // If we are in the list, the list will update via local cache anyway or on next fetch
      },
    );
  }

  Future<void> _onMessageReceived(ChatEventMessageReceived event, Emitter<ChatState> emit) async {
    final message = event.message;
    final currentChat = state.maybeMap(
      chatSelected: (s) => s.chat,
      messagesLoaded: (s) => s.chat,
      orElse: () => null,
    );

    if (currentChat != null && currentChat.id == message.chatId) {
      final List<Message> currentMessages = state.maybeMap(
        chatSelected: (s) => s.messages,
        messagesLoaded: (s) => s.messages,
        orElse: () => [],
      );

      final List<Message> mutableList = List<Message>.from(currentMessages);

      // Check if this is a message from me that should replace a temp one
      if (message.senderId == _currentUserId) {
        // Look for a temp message with same content and type
        final tempIdx = mutableList.indexWhere((m) => 
          m.id.startsWith('temp_') && 
          m.messageType.toLowerCase() == message.messageType.toLowerCase() &&
          (m.content.trim() == message.content.trim() || (m.content.isEmpty && message.content.isEmpty)));
        
        if (tempIdx != -1) {
          // Replace temp with real one
          // ignore: avoid_print
          print('[DEBUG] Smart replacement (SignalR): replacing ${mutableList[tempIdx].id} with ${message.id} (Type: ${message.messageType})');
          mutableList[tempIdx] = message;
          emit(ChatState.messagesLoaded(currentChat, mutableList, typingUsers: state.typingUsers));
          return;
        }
        
        // If no temp message found, check if we already have this message by ID (prevent double add)
        if (mutableList.any((m) => m.id == message.id)) {
          // ignore: avoid_print
          print('[DEBUG] Self-message already in list, skipping: ${message.id}');
          return;
        }
        // ignore: avoid_print
        print('[DEBUG] Self-message received but no matching temp found: ${message.id} (Content: "${message.content}", Type: ${message.messageType})');
      } else {
        // From someone else, check for duplicates
        if (mutableList.any((m) => m.id == message.id)) return;
      }

      final updatedMessages = mutableList..insert(0, message);
      emit(ChatState.messagesLoaded(currentChat, updatedMessages, typingUsers: state.typingUsers));
    }
    
    // Also trigger chats refresh if we are in chatsLoaded state to update last message/unread
    state.maybeMap(
      chatsLoaded: (_) => add(ChatEvent.chatsLoaded(currentUserId: currentChat?.participants?.firstWhere((p) => p.id != '').id)), // userId logic might need to be better
      orElse: () => null,
    );
  }

  Future<void> _onMessageSent(ChatEventMessageSent event, Emitter<ChatState> emit) async {
    final currentChat = state.maybeMap(
      chatSelected: (s) => s.chat,
      messagesLoaded: (s) => s.chat,
      orElse: () => null,
    );

    if (currentChat == null) return;
    final userId = _currentUserId ?? '';

    final List<Message> currentMessages = state.maybeMap(
      chatSelected: (s) => s.messages,
      messagesLoaded: (s) => s.messages,
      orElse: () => [],
    );

    // Create optimistic "sending" message
    final tempId = 'temp_${DateTime.now().millisecondsSinceEpoch}';
    final tempMessage = Message(
      id: tempId,
      chatId: event.chatId,
      senderId: userId,
      content: event.content,
      messageType: 'text',
      sequenceId: -1, // Temporary
      createdAt: DateTime.now(),
      status: MessageStatus.sending,
    );

    final updatedMessages = List<Message>.from(currentMessages)..insert(0, tempMessage);
    emit(ChatState.messagesLoaded(currentChat, updatedMessages, typingUsers: state.typingUsers));

    final result = await chatRepository.sendMessage(event.chatId, event.content);
    
    result.fold(
      (error) {
        // ignore: avoid_print
        print('[DEBUG] sendMessage failed for $tempId: ${error.toString()}');
        final isNetworkError = error.maybeMap(
          network: (_) => true,
          orElse: () => false,
        );

        if (isNetworkError) {
          // Keep as sending, retry timer will pick it up
          return;
        }

        final List<Message> currentList = state.maybeMap(
          messagesLoaded: (s) => s.messages,
          orElse: () => updatedMessages,
        );
        
        final List<Message> mutableList = List<Message>.from(currentList);
        final idx = mutableList.indexWhere((m) => m.id == tempId);
        if (idx != -1) {
          mutableList[idx] = mutableList[idx].copyWith(status: MessageStatus.error);
          emit(ChatState.messagesLoaded(currentChat, mutableList, typingUsers: state.typingUsers));
        }
      },
      (sentMessage) {
        // Replace temp message with the actual one from server
        // ignore: avoid_print
        print('[DEBUG] sendMessage success for $tempId, replacing with ${sentMessage.id}');
        final List<Message> currentList = state.maybeMap(
          messagesLoaded: (s) => s.messages,
          orElse: () => updatedMessages,
        );
        
        final List<Message> mutableList = List<Message>.from(currentList);
        final idx = mutableList.indexWhere((m) => m.id == tempId);
        if (idx != -1) {
          mutableList[idx] = sentMessage;
          emit(ChatState.messagesLoaded(currentChat, mutableList, typingUsers: state.typingUsers));
        }
      },
    );
  }

  Future<void> _onFavoritesRequested(ChatEventFavoritesRequested event, Emitter<ChatState> emit) async {
    emit(ChatState.loading(typingUsers: state.typingUsers));
    final result = await chatRepository.getOrCreateFavorites();
    result.fold(
      (error) => emit(ChatState.error(error.message ?? 'Failed to open Favorites', typingUsers: state.typingUsers)),
      (chat) => add(ChatEvent.chatSelected(chat.id)),
    );
  }

  Future<void> _onTypingUpdated(ChatEventTypingUpdated event, Emitter<ChatState> emit) async {
    final Map<String, Set<String>> newTypingUsers = Map.from(state.typingUsers);
    final Set<String> chatTypingUsers = Set.from(newTypingUsers[event.chatId] ?? {});
    
    if (event.isTyping) {
      chatTypingUsers.add(event.userId);
    } else {
      chatTypingUsers.remove(event.userId);
    }
    
    if (chatTypingUsers.isEmpty) {
      newTypingUsers.remove(event.chatId);
    } else {
      newTypingUsers[event.chatId] = chatTypingUsers;
    }
    
    state.maybeMap(
      chatSelected: (s) {
        // If it's the current chat, transition to stable state
        if (s.chat.id == event.chatId) {
          emit(ChatState.messagesLoaded(s.chat, s.messages, typingUsers: newTypingUsers));
        } else {
          emit(s.copyWith(typingUsers: newTypingUsers));
        }
      },
      messagesLoaded: (s) => emit(s.copyWith(typingUsers: newTypingUsers)),
      chatsLoaded: (s) => emit(s.copyWith(typingUsers: newTypingUsers)),
      orElse: () => emit(state.copyWith(typingUsers: newTypingUsers)),
    );
  }

  Future<void> _onUserStatusChanged(ChatEventUserStatusChanged event, Emitter<ChatState> emit) async {
    state.mapOrNull(
      chatsLoaded: (s) {
        final updatedChats = s.chats.map((chat) {
          final participants = chat.participants;
          if (participants != null && participants.any((p) => p.id == event.userId)) {
            final updatedParticipants = participants.map((p) {
              if (p.id == event.userId) {
                return p.copyWith(isOnline: event.isOnline, lastSeen: event.lastSeen ?? p.lastSeen);
              }
              return p;
            }).toList();
            return chat.copyWith(participants: updatedParticipants);
          }
          return chat;
        }).toList();
        emit(s.copyWith(chats: updatedChats));
      },
      chatSelected: (s) {
        if (s.chat.participants?.any((p) => p.id == event.userId) ?? false) {
          final updatedParticipants = s.chat.participants!.map((p) {
            if (p.id == event.userId) {
              return p.copyWith(isOnline: event.isOnline, lastSeen: event.lastSeen ?? p.lastSeen);
            }
            return p;
          }).toList();
          final updatedChat = s.chat.copyWith(participants: updatedParticipants);
          emit(s.copyWith(chat: updatedChat));
        }
      },
      messagesLoaded: (s) {
        if (s.chat.participants?.any((p) => p.id == event.userId) ?? false) {
          final updatedParticipants = s.chat.participants!.map((p) {
            if (p.id == event.userId) {
              return p.copyWith(isOnline: event.isOnline, lastSeen: event.lastSeen ?? p.lastSeen);
            }
            return p;
          }).toList();
          final updatedChat = s.chat.copyWith(participants: updatedParticipants);
          emit(s.copyWith(chat: updatedChat));
        }
      },
    );
  }

  Future<void> _onRefreshPresence(ChatEventRefreshPresence event, Emitter<ChatState> emit) async {
    // Re-emit the current state with a new timestamp to trigger UI rebuild
    state.mapOrNull(
      initial: (s) => emit(s.copyWith(lastUpdate: DateTime.now())),
      loading: (s) => emit(s.copyWith(lastUpdate: DateTime.now())),
      chatsLoaded: (s) => emit(s.copyWith(lastUpdate: DateTime.now())),
      chatSelected: (s) => emit(s.copyWith(lastUpdate: DateTime.now())),
      messagesLoaded: (s) => emit(s.copyWith(lastUpdate: DateTime.now())),
      error: (s) => emit(s.copyWith(lastUpdate: DateTime.now())),
    );
  }

  Future<void> _onSendTypingStatus(ChatEventSendTypingStatus event, Emitter<ChatState> emit) async {
    await chatRepository.sendTypingStatus(event.chatId, event.isTyping);
  }

  Future<void> _onMarkAsRead(ChatEventMarkAsRead event, Emitter<ChatState> emit) async {
    await chatRepository.markAsRead(event.chatId, event.messageId, event.sequenceId);
  }

  Future<void> _onReadReceiptReceived(ChatEventReadReceiptReceived event, Emitter<ChatState> emit) async {
    state.mapOrNull(
      messagesLoaded: (s) {
        if (s.chat.id == event.chatId) {
          final updatedMessages = s.messages.map((m) {
            if (m.sequenceId <= event.lastReadSequenceId && m.status != MessageStatus.read) {
              return m.copyWith(status: MessageStatus.read);
            }
            return m;
          }).toList();
          emit(s.copyWith(messages: updatedMessages));
        }
      },
      chatSelected: (s) {
        if (s.chat.id == event.chatId) {
          final updatedMessages = s.messages.map((m) {
            if (m.sequenceId <= event.lastReadSequenceId && m.status != MessageStatus.read) {
              return m.copyWith(status: MessageStatus.read);
            }
            return m;
          }).toList();
          emit(s.copyWith(messages: updatedMessages));
        }
      },
    );
  }

  Future<void> _onCacheCleared(ChatEventCacheCleared event, Emitter<ChatState> emit) async {
    // ignore: avoid_print
    print('[DEBUG] ChatBloc._onCacheCleared START');
    await chatRepository.clearCache();
    // ignore: avoid_print
    print('[DEBUG] ChatBloc._onCacheCleared END');
    emit(const ChatState.initial());
  }

  void _retryPendingMessages() {
    state.mapOrNull(
      messagesLoaded: (s) {
        final pending = s.messages.where((m) => m.status == MessageStatus.sending && m.id.startsWith('temp_')).toList();
        for (final msg in pending) {
          if (!_sendingIds.contains(msg.id)) {
            _retrySendMessage(msg);
          }
        }
      },
    );
  }

  Future<void> _retrySendMessage(Message tempMessage) async {
    _sendingIds.add(tempMessage.id);
    final result = await chatRepository.sendMessage(tempMessage.chatId, tempMessage.content);
    _sendingIds.remove(tempMessage.id);

    result.fold(
      (error) {
        final isNetworkError = error.maybeMap(
          network: (_) => true,
          orElse: () => false,
        );

        if (!isNetworkError) {
          _updateMessageStatus(tempMessage.id, MessageStatus.error);
        }
      },
      (sentMessage) {
        _replaceTempMessage(tempMessage.id, sentMessage);
      },
    );
  }

  void _updateMessageStatus(String id, MessageStatus status) {
    add(ChatEvent.messageStatusUpdated(id, status));
  }
  
  void _replaceTempMessage(String tempId, Message realMessage) {
    add(ChatEvent.messageReplaced(tempId, realMessage));
  }

  Future<void> _onMessageStatusUpdated(ChatEventMessageStatusUpdated event, Emitter<ChatState> emit) async {
    state.mapOrNull(
      messagesLoaded: (s) {
        final List<Message> mutableList = List<Message>.from(s.messages);
        final idx = mutableList.indexWhere((m) => m.id == event.messageId);
        if (idx != -1) {
          mutableList[idx] = mutableList[idx].copyWith(status: event.status);
          emit(s.copyWith(messages: mutableList));
        }
      },
    );
  }

  Future<void> _onMessageReplaced(ChatEventMessageReplaced event, Emitter<ChatState> emit) async {
    state.mapOrNull(
      messagesLoaded: (s) {
        final List<Message> mutableList = List<Message>.from(s.messages);
        final idx = mutableList.indexWhere((m) => m.id == event.tempId);
        if (idx != -1) {
          mutableList[idx] = event.realMessage;
          emit(s.copyWith(messages: mutableList));
        }
      },
    );
  }

  @override
  Future<void> close() {
    _messageSubscription?.cancel();
    _typingSubscription?.cancel();
    _statusSubscription?.cancel();
    _readReceiptSubscription?.cancel();
    _refreshTimer?.cancel();
    _retryTimer?.cancel();
    chatRepository.disposeSignalR();
    return super.close();
  }
}
