import 'dart:convert';

import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:kasy_kit/core/config/app_env.dart';
import 'package:kasy_kit/core/states/translations.dart';
import 'package:kasy_kit/core/states/user_state_notifier.dart';
import 'package:kasy_kit/features/llm_chat/api/llm_chat_api.dart';
import 'package:kasy_kit/features/llm_chat/api/llm_chat_message_entity.dart';
import 'package:logger/logger.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

const int _kMaxContextMessages = 20;

class ChatMessage {
  final String role;
  final String content;

  const ChatMessage({required this.role, required this.content});

  factory ChatMessage.user(String content) =>
      ChatMessage(role: 'user', content: content);

  factory ChatMessage.assistant(String content) =>
      ChatMessage(role: 'assistant', content: content);

  factory ChatMessage.fromEntity(LlmChatMessageEntity entity) =>
      ChatMessage(role: entity.role, content: entity.content);

  LlmChatMessageEntity toEntity() => LlmChatMessageEntity(
    role: role,
    content: content,
    createdAt: DateTime.now(),
  );
}

class LlmChatState {
  final List<ChatMessage> messages;
  final bool isReplying;
  final bool streamingStarted;

  const LlmChatState({
    required this.messages,
    this.isReplying = false,
    this.streamingStarted = false,
  });

  LlmChatState copyWith({
    List<ChatMessage>? messages,
    bool? isReplying,
    bool? streamingStarted,
  }) {
    return LlmChatState(
      messages: messages ?? this.messages,
      isReplying: isReplying ?? this.isReplying,
      streamingStarted: streamingStarted ?? this.streamingStarted,
    );
  }
}

final llmChatNotifierProvider =
    AsyncNotifierProvider<LlmChatNotifier, LlmChatState>(LlmChatNotifier.new);

class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
  final Logger _logger = Logger();

  @override
  Future<LlmChatState> build() async {
    final userId = ref.read(userStateNotifierProvider).user.idOrNull;
    if (userId == null) return const LlmChatState(messages: []);

    try {
      final entities = await ref.read(llmChatApiProvider).loadMessages(userId);
      return LlmChatState(
        messages: entities.map(ChatMessage.fromEntity).toList(),
      );
    } catch (e) {
      _logger.e('Failed to load LLM chat history: $e');
      return const LlmChatState(messages: []);
    }
  }

  Future<void> sendMessage(String prompt) async {
    final current = switch (state) {
      AsyncData(:final value) => value,
      _ => null,
    };
    if (current == null || current.isReplying) return;

    final userMsg = ChatMessage.user(prompt);
    state = AsyncData(
      current.copyWith(
        messages: [...current.messages, userMsg],
        isReplying: true,
        streamingStarted: false,
      ),
    );

    _persistMessage(userMsg);

    final allMessages = (state as AsyncData<LlmChatState>).value.messages;
    final history = allMessages.length > _kMaxContextMessages
        ? allMessages.sublist(allMessages.length - _kMaxContextMessages)
        : allMessages;
    await _requestReplyStream(history);
  }

  Future<void> _requestReplyStream(List<ChatMessage> history) async {
    final t = ref.read(translationsProvider).llm_chat;
    final String llmChatEndpoint = AppEnv.llmChatEndpoint;

    if (llmChatEndpoint.isEmpty) {
      _finalizeAssistantMessage(t.error_not_configured);
      return;
    }

    final accessToken =
        Supabase.instance.client.auth.currentSession?.accessToken;
    if (accessToken == null) {
      _finalizeAssistantMessage(t.error_not_configured);
      return;
    }

    final dio = Dio(
      BaseOptions(
        headers: {
          'Authorization': 'Bearer $accessToken',
          'Content-Type': 'application/json',
        },
      ),
    );

    try {
      final response = await dio.post<ResponseBody>(
        llmChatEndpoint,
        data: {
          'message': history.last.content,
          'history': history
              .map((e) => {'role': e.role, 'content': e.content})
              .toList(growable: false),
        },
        options: Options(responseType: ResponseType.stream),
      );

      final lineBuffer = StringBuffer();
      final contentBuffer = StringBuffer();

      await for (final chunk in response.data!.stream) {
        final text = utf8.decode(chunk, allowMalformed: true);
        lineBuffer.write(text);

        final raw = lineBuffer.toString();
        final lastNewline = raw.lastIndexOf('\n');
        if (lastNewline == -1) continue;

        final completeSection = raw.substring(0, lastNewline + 1);
        lineBuffer.clear();
        if (lastNewline < raw.length - 1) {
          lineBuffer.write(raw.substring(lastNewline + 1));
        }

        for (final line in completeSection.split('\n')) {
          final delta = _extractSSEDelta(line.trimRight());
          if (delta.isEmpty) continue;

          contentBuffer.write(delta);
          _appendStreamingChunk(contentBuffer.toString());
        }
      }

      final fullContent = contentBuffer.toString();
      if (fullContent.isNotEmpty) {
        _persistMessage(ChatMessage.assistant(fullContent));
      } else {
        _finalizeAssistantMessage(t.error_no_reply);
        return;
      }

      final latest = switch (state) {
        AsyncData(:final value) => value,
        _ => null,
      };
      if (latest != null) {
        state = AsyncData(
          latest.copyWith(isReplying: false, streamingStarted: false),
        );
      }
    } catch (error) {
      _logger.e('LLM chat stream failed: $error');
      _finalizeAssistantMessage(t.error_network);
    }
  }

  void _appendStreamingChunk(String partialContent) {
    final current = switch (state) {
      AsyncData(:final value) => value,
      _ => null,
    };
    if (current == null) return;

    final msgs = current.messages;
    final newMsgs = current.streamingStarted
        ? [
            ...msgs.sublist(0, msgs.length - 1),
            ChatMessage.assistant(partialContent),
          ]
        : [...msgs, ChatMessage.assistant(partialContent)];

    state = AsyncData(
      LlmChatState(messages: newMsgs, isReplying: true, streamingStarted: true),
    );
  }

  void _finalizeAssistantMessage(String content) {
    final current = switch (state) {
      AsyncData(:final value) => value,
      _ => const LlmChatState(messages: []),
    };
    final msgs = current.messages;
    final newMsgs = current.streamingStarted
        ? [...msgs.sublist(0, msgs.length - 1), ChatMessage.assistant(content)]
        : [...msgs, ChatMessage.assistant(content)];
    state = AsyncData(LlmChatState(messages: newMsgs));
  }

  String _extractSSEDelta(String line) {
    if (!line.startsWith('data: ')) return '';
    final payload = line.substring(6);
    if (payload == '[DONE]') return '';
    try {
      final json = jsonDecode(payload) as Map<String, dynamic>;

      final choices = json['choices'] as List?;
      if (choices != null && choices.isNotEmpty) {
        final delta =
            (choices.first as Map<String, dynamic>)['delta']
                as Map<String, dynamic>?;
        return (delta?['content'] as String?) ?? '';
      }

      final candidates = json['candidates'] as List?;
      if (candidates != null && candidates.isNotEmpty) {
        final content =
            (candidates.first as Map<String, dynamic>)['content']
                as Map<String, dynamic>?;
        final parts = content?['parts'] as List?;
        if (parts != null && parts.isNotEmpty) {
          return ((parts.first as Map<String, dynamic>)['text'] as String?) ??
              '';
        }
      }
    } catch (_) {}
    return '';
  }

  void _persistMessage(ChatMessage message) {
    final userId = ref.read(userStateNotifierProvider).user.idOrNull;
    if (userId == null) return;
    ref
        .read(llmChatApiProvider)
        .saveMessage(userId, message.toEntity())
        .catchError((e) {
          _logger.e('Failed to persist message: $e');
        });
  }
}
