import 'dart:convert';

import 'package:flutter_bloc/flutter_bloc.dart';

import '../../data/models/ag_ui_event.dart';
import '../../data/models/chat_list_item.dart';
import '../../data/models/tool_result.dart';
import '../../data/services/ag_ui_service.dart';

class ChatState {
  final List<ChatListItem> items;
  final bool isLoading;
  final String? currentMessageId;
  final String? error;
  final DateTime? oldestLoadedDate;
  final bool hasEarlierHistory;

  const ChatState({
    this.items = const [],
    this.isLoading = false,
    this.currentMessageId,
    this.error,
    this.oldestLoadedDate,
    this.hasEarlierHistory = false,
  });

  static const _unset = Object();

  ChatState copyWith({
    List<ChatListItem>? items,
    bool? isLoading,
    Object? currentMessageId = _unset,
    Object? error = _unset,
    Object? oldestLoadedDate = _unset,
    bool? hasEarlierHistory,
  }) {
    return ChatState(
      items: items ?? this.items,
      isLoading: isLoading ?? this.isLoading,
      currentMessageId: currentMessageId == _unset
          ? this.currentMessageId
          : currentMessageId as String?,
      error: error == _unset ? this.error : error as String?,
      oldestLoadedDate: oldestLoadedDate == _unset
          ? this.oldestLoadedDate
          : oldestLoadedDate as DateTime?,
      hasEarlierHistory: hasEarlierHistory ?? this.hasEarlierHistory,
    );
  }
}

class ChatBloc extends Cubit<ChatState> {
  final AgUiService _service;
  final Map<String, String> _toolCallArgsBuffer = {};

  ChatBloc({AgUiService? service})
    : _service = service ?? AgUiService(),
      super(const ChatState()) {
    _service.onEvent = _handleEvent;
  }

  void _handleEvent(AgUiEvent event) {
    switch (event.type) {
      case AgUiEventType.runStarted:
        emit(state.copyWith(isLoading: true, error: null));
      case AgUiEventType.runFinished:
        emit(state.copyWith(isLoading: false, currentMessageId: null));
      case AgUiEventType.runError:
        final errorEvent = event as RunErrorEvent;
        emit(state.copyWith(isLoading: false, error: errorEvent.message));
      case AgUiEventType.textMessageStart:
        _handleTextMessageStart(event as TextMessageStartEvent);
      case AgUiEventType.textMessageContent:
        _handleTextMessageContent(event as TextMessageContentEvent);
      case AgUiEventType.textMessageEnd:
        _handleTextMessageEnd(event as TextMessageEndEvent);
      case AgUiEventType.toolCallStart:
        _handleToolCallStart(event as ToolCallStartEvent);
      case AgUiEventType.toolCallArgs:
        _handleToolCallArgs(event as ToolCallArgsEvent);
      case AgUiEventType.toolCallEnd:
        _handleToolCallEnd(event as ToolCallEndEvent);
      case AgUiEventType.toolCallResult:
        _handleToolCallResult(event as ToolCallResultEvent);
      case AgUiEventType.toolCallError:
        _handleToolCallError(event as ToolCallErrorEvent);
      case AgUiEventType.messagesSnapshot:
        _handleMessagesSnapshot(event as MessagesSnapshotEvent);
      case AgUiEventType.unknown:
        break;
    }
  }

  void _handleTextMessageStart(TextMessageStartEvent startEvent) {
    final newMessage = TextMessageItem(
      id: startEvent.messageId,
      content: '',
      timestamp: DateTime.now(),
      sender: MessageSender.ai,
      isStreaming: true,
    );
    emit(
      state.copyWith(
        items: [...state.items, newMessage],
        currentMessageId: startEvent.messageId,
      ),
    );
  }

  void _handleTextMessageContent(TextMessageContentEvent contentEvent) {
    final updatedItems = state.items.map((item) {
      if (item.id == contentEvent.messageId && item is TextMessageItem) {
        return item.copyWith(content: item.content + contentEvent.delta);
      }
      return item;
    }).toList();
    emit(state.copyWith(items: updatedItems));
  }

  void _handleTextMessageEnd(TextMessageEndEvent endEvent) {
    final updatedItems = state.items.map((item) {
      if (item.id == endEvent.messageId && item is TextMessageItem) {
        return item.copyWith(isStreaming: false);
      }
      return item;
    }).toList();
    emit(state.copyWith(items: updatedItems, currentMessageId: null));
  }

  void _handleToolCallStart(ToolCallStartEvent startEvent) {
    _toolCallArgsBuffer[startEvent.toolCallId] = '';
    final newToolCall = ToolCallItem(
      id: startEvent.toolCallId,
      callId: startEvent.toolCallId,
      toolName: startEvent.toolCallName,
      args: {},
      status: ToolCallStatus.pending,
      timestamp: DateTime.now(),
      sender: MessageSender.ai,
    );
    emit(state.copyWith(items: [...state.items, newToolCall]));
  }

  void _handleToolCallArgs(ToolCallArgsEvent argsEvent) {
    _toolCallArgsBuffer[argsEvent.toolCallId] =
        (_toolCallArgsBuffer[argsEvent.toolCallId] ?? '') + argsEvent.delta;
  }

  void _handleToolCallEnd(ToolCallEndEvent endEvent) {
    final argsBuffer = _toolCallArgsBuffer[endEvent.toolCallId] ?? '';
    Map<String, dynamic> parsedArgs = {};
    if (argsBuffer.isNotEmpty) {
      try {
        parsedArgs = jsonDecode(argsBuffer) as Map<String, dynamic>;
      } catch (_) {}
    }
    _toolCallArgsBuffer.remove(endEvent.toolCallId);
    final updatedItems = state.items.map((item) {
      if (item.id == endEvent.toolCallId && item is ToolCallItem) {
        return item.copyWith(
          args: parsedArgs,
          status: ToolCallStatus.executing,
        );
      }
      return item;
    }).toList();
    emit(state.copyWith(items: updatedItems));
  }

  void _handleToolCallResult(ToolCallResultEvent resultEvent) {
    final filteredItems = state.items.where((item) {
      if (item.id == resultEvent.toolCallId && item is ToolCallItem) {
        return false;
      }
      return true;
    }).toList();
    final resultItem = ToolResultItem(
      id: resultEvent.messageId,
      callId: resultEvent.toolCallId,
      uiCard: resultEvent.ui ?? UiCard(cardType: 'empty', data: {}),
      timestamp: DateTime.now(),
      sender: MessageSender.ai,
    );
    emit(state.copyWith(items: [...filteredItems, resultItem]));
  }

  void _handleToolCallError(ToolCallErrorEvent errorEvent) {
    _toolCallArgsBuffer.remove(errorEvent.toolCallId);
    final updatedItems = state.items.map((item) {
      if (item.id == errorEvent.toolCallId && item is ToolCallItem) {
        return item.copyWith(
          status: ToolCallStatus.error,
          errorMessage: errorEvent.error,
        );
      }
      return item;
    }).toList();
    emit(state.copyWith(items: updatedItems));
  }

  void _handleMessagesSnapshot(MessagesSnapshotEvent snapshotEvent) {
    final newItems = _convertSnapshotMessages(snapshotEvent.messages);
    final allItems = [...newItems, ...state.items];

    // Determine oldest date and history availability
    DateTime? newOldestDate = state.oldestLoadedDate;
    bool newHasEarlierHistory = false;

    if (newItems.isNotEmpty) {
      newOldestDate = _extractDateFromItems(newItems);
      if (newOldestDate != null) {
        newHasEarlierHistory = _service.hasEarlierHistory(newOldestDate);
      }
    } else if (newOldestDate != null) {
      newHasEarlierHistory = _service.hasEarlierHistory(newOldestDate);
    }

    emit(
      state.copyWith(
        items: allItems,
        oldestLoadedDate: newOldestDate,
        hasEarlierHistory: newHasEarlierHistory,
      ),
    );
  }

  List<ChatListItem> _convertSnapshotMessages(List<SnapshotMessage> messages) {
    return messages.map((msg) {
      final timestamp = msg.timestamp ?? DateTime.now();
      switch (msg.role) {
        case 'user':
          return TextMessageItem(
            id: msg.id,
            content: msg.content ?? '',
            timestamp: timestamp,
            sender: MessageSender.user,
          );
        case 'assistant':
          return TextMessageItem(
            id: msg.id,
            content: msg.content ?? '',
            timestamp: timestamp,
            sender: MessageSender.ai,
          );
        case 'tool' when msg.ui != null:
          return ToolResultItem(
            id: msg.id,
            callId: msg.toolCallId ?? '',
            uiCard: msg.ui!,
            timestamp: timestamp,
            sender: MessageSender.ai,
          );
        default:
          return TextMessageItem(
            id: msg.id,
            content: msg.content ?? '',
            timestamp: timestamp,
            sender: MessageSender.ai,
          );
      }
    }).toList();
  }

  DateTime? _extractDateFromItems(List<ChatListItem> items) {
    if (items.isEmpty) return null;

    return items
        .map(
          (item) => DateTime(
            item.timestamp.year,
            item.timestamp.month,
            item.timestamp.day,
          ),
        )
        .reduce((a, b) => a.isBefore(b) ? a : b);
  }

  Future<void> sendMessage(String content) async {
    final userMessage = TextMessageItem(
      id: 'user-${DateTime.now().millisecondsSinceEpoch}',
      content: content,
      timestamp: DateTime.now(),
      sender: MessageSender.user,
    );
    emit(state.copyWith(items: [...state.items, userMessage]));
    await _service.sendMessage(content);
  }

  Future<void> loadHistory() async {
    if (state.isLoading) return;
    await _service.loadHistory();
  }

  Future<void> loadMoreHistory() async {
    if (state.isLoading || !state.hasEarlierHistory) return;
    if (state.oldestLoadedDate == null) return;

    await _service.loadHistory(beforeDate: state.oldestLoadedDate);
  }

  void clearError() {
    emit(state.copyWith(error: null));
  }
}
