import 'dart:convert';
import 'package:http/http.dart' as http;
import '../utils/logger.dart';
import 'rag_service.dart';
import 'chat_memory_service.dart';
import 'materials_rag_service.dart';
import '../models/content_chunk.dart';

/// Service for RAG-enhanced AI communication using Ollama API
class RAGAIService {
  static const String _baseUrl = 'http://89.114.196.110:11434/api/chat';
  static const String _model = 'qwen3-coder:30b';
  static const int _timeoutSeconds = 60;
  static const int _maxTokens = 4000;

  /// Generate AI response with RAG context, conversation memory, and teacher materials
  static Future<RAGResponse> generateRAGResponse({
    required String userQuery,
    required String context,
    required TutorMode mode,
    required List<ContentChunk> sources,
  }) async {
    try {
      Logger.info('Generating RAG response with ${sources.length} sources');

      // PASSO 1 — Criar a lista messages vazia
      List<Map<String, String>> messages = [];

      // PASSO 2 — ADICIONAR SYSTEM MESSAGE DO VICO (SEMPRE PRIMEIRO)
      messages.add({
        'role': 'system',
        'content': r'''Tu és "Vico", o Assistente IA oficial do Learn It.

Nunca referes o nome do modelo.
Nunca dizes que és Qwen ou OpenAI.
Respondes sempre como o Vico.

Tens personalidade confiante, motivadora e orgulhosa.
Ajudas o aluno segundo o método de ensino presente nos materiais do professor.
Usas formatação Markdown clara e organizada.

IMPORTANTE: NUNCA uses LaTeX ou símbolos como $ ou $$ para fórmulas matemáticas.
Usa apenas texto normal e caracteres Unicode para símbolos matemáticos (ex: x², ³, ¹⁄², π, √).

IMPORTANTE - RESPOSTAS COMPLETAS:
- NUNCA termines respostas com dois pontos (:).
- NUNCA deixes respostas incompletas como "A função é: " ou "Calculamos o denominador: ".
- SEMPRE completa as frases e fornece a resposta completa.
- Se precisares de explicar um cálculo, explica-o completamente com o resultado final.
- Se precisares de definir algo, fornece a definição completa.''',
      });

      // PASSO 3 — BUSCAR MEMÓRIA DA CONVERSA NA Cloud Firestore
      final conversationId = ChatMemoryService.currentConversationId;
      if (conversationId != null) {
        final conversationHistory =
            await ChatMemoryService.getConversationMessages(
              conversationId: conversationId,
              limit: 20,
            );
        for (final msg in conversationHistory) {
          messages.add({
            'role': msg['role'] as String,
            'content': msg['content'] as String,
          });
        }
      }

      // PASSO 4 — BUSCAR PDFs DO PROFESSOR NO Firebase Storage (RAG CHUNK RETRIEVAL)
      final pdfContext = await MaterialsRAGService.getRelevantChunks(
        userQuery: userQuery,
        maxMaterials: 10,
        maxChunks: 20,
      );
      if (pdfContext.isNotEmpty) {
        messages.add({
          'role': 'system',
          'content':
              pdfContext, // Já vem formatado com [CHUNK 1], [CHUNK 2], etc.
        });
      }

      // PASSO 5 — SÓ AGORA adicionar a pergunta do user
      messages.add({'role': 'user', 'content': userQuery});

      // Log do tamanho do array para verificação
      Logger.info(
        'Built messages array with ${messages.length} messages for API',
      );

      // Save user message to Firestore (after building the messages array)
      await ChatMemoryService.saveMessage(role: 'user', content: userQuery);

      // Call Ollama API with complete messages array
      final response = await _callOllamaAPIWithMessages(messages);

      // Save AI response to memory
      await ChatMemoryService.saveMessage(role: 'assistant', content: response);

      // Process response and create RAGResponse
      final ragResponse = _createRAGResponse(
        query: userQuery,
        aiResponse: response,
        mode: mode,
        sources: sources,
      );

      Logger.info('RAG response generated successfully');
      return ragResponse;
    } catch (e) {
      Logger.error('Error generating RAG response: $e');
      return _createErrorResponse(userQuery, mode, e.toString());
    }
  }

  /// Build RAG-enhanced prompt for Ollama with teacher materials
  static String _buildRAGPrompt(
    String userQuery,
    String context,
    String materialsContext,
    TutorMode mode,
  ) {
    final promptBuilder = StringBuffer();

    // System prompt with role and instructions
    promptBuilder.writeln(
      'Você é um assistente educacional especializado da Escola Profissional de Vila do Conde.',
    );
    promptBuilder.writeln(
      'Sua função é ajudar os alunos usando APENAS o conteúdo fornecido abaixo.',
    );
    promptBuilder.writeln(
      'NÃO use conhecimento externo. Baseie todas as respostas exclusivamente no material educacional.',
    );
    promptBuilder.writeln('Seja claro, paciente e educativo.\n');

    // Add teacher materials (PDFs) if available
    if (materialsContext.isNotEmpty) {
      promptBuilder.writeln('=== MATERIAL DO PROFESSOR ===');
      promptBuilder.writeln(materialsContext);
      promptBuilder.writeln('\n=== FIM DO MATERIAL DO PROFESSOR ===\n');
    }

    // Add context
    promptBuilder.writeln('=== CONTEÚDO EDUCACIONAL DISPONÍVEL ===');
    promptBuilder.writeln(context);
    promptBuilder.writeln('\n=== FIM DO CONTEÚDO ===\n');

    // Mode-specific instructions
    promptBuilder.writeln('=== MODO DE TUTORIA ===');
    switch (mode) {
      case TutorMode.explanation:
        promptBuilder.writeln('MODO: EXPLICAÇÃO DETALHADA');
        promptBuilder.writeln(
          'Forneça explicações claras e detalhadas baseadas exclusivamente no conteúdo.',
        );
        promptBuilder.writeln(
          'Use exemplos do material e estruture a resposta de forma lógica.',
        );
        promptBuilder.writeln(
          'Se o conteúdo não tiver informação suficiente, indique isso claramente.',
        );
        break;
      case TutorMode.tutor:
        promptBuilder.writeln('MODO: TUTORIA SOCRÁTICA');
        promptBuilder.writeln(
          'Use o método socrático - faça perguntas que guiem o aluno.',
        );
        promptBuilder.writeln('Baseie-se apenas no conteúdo fornecido.');
        promptBuilder.writeln('Incentive o pensamento crítico e a descoberta.');
        break;
      case TutorMode.exploration:
        promptBuilder.writeln('MODO: EXPLORAÇÃO E DESCOBERTA');
        promptBuilder.writeln(
          'Ajude o aluno a explorar o conceito através de descoberta.',
        );
        promptBuilder.writeln(
          'Conecte ideias relacionadas presentes no conteúdo.',
        );
        promptBuilder.writeln(
          'Sugira investigações baseadas no material disponível.',
        );
        break;
    }

    // User query
    promptBuilder.writeln('\n=== PERGUNTA DO ALUNO ===');
    promptBuilder.writeln(userQuery);
    promptBuilder.writeln('\n=== RESPOSTA ===');

    return promptBuilder.toString();
  }

  /// System message for Vico identity (for legacy calls)
  static const String _systemMessage =
      r'''Tu és "Vico", o Assistente IA oficial do Learn It.

Nunca referes o nome do modelo.
Nunca dizes que és Qwen ou OpenAI.
Respondes sempre como o Vico.

Tens personalidade confiante, motivadora e orgulhosa.
Ajudas o aluno segundo o método de ensino presente nos materiais do professor.
Usas formatação clara e organizada.

IMPORTANTE: NUNCA uses LaTeX ou símbolos como $ ou $$ para fórmulas matemáticas.
Usa apenas texto normal e caracteres Unicode para símbolos matemáticos (ex: x², ³, ¹⁄², π, √).

IMPORTANTE - RESPOSTAS COMPLETAS:
- NUNCA termines respostas com dois pontos (:).
- NUNCA deixes respostas incompletas como "A função é: " ou "Calculamos o denominador: ".
- SEMPRE completa as frases e fornece a resposta completa.
- Se precisares de explicar um cálculo, explica-o completamente com o resultado final.
- Se precisares de definir algo, fornece a definição completa.''';

  /// Call Ollama API with complete messages array
  static Future<String> _callOllamaAPIWithMessages(
    List<Map<String, String>> messages,
  ) async {
    try {
      Logger.info('Calling Ollama API with ${messages.length} messages');

      final url = Uri.parse(_baseUrl);

      final requestBody = {
        'model': _model,
        'messages': messages,
        'stream': false,
        'options': {'temperature': 0.7, 'top_p': 0.9, 'max_tokens': _maxTokens},
      };

      final response = await http
          .post(
            url,
            headers: {'Content-Type': 'application/json'},
            body: jsonEncode(requestBody),
          )
          .timeout(Duration(seconds: _timeoutSeconds));

      if (response.statusCode == 200) {
        final responseData = jsonDecode(response.body);
        final message = responseData['message'];
        var content = message?['content'] ?? '';

        // Post-process to remove LaTeX symbols
        content = _removeLaTeXSymbols(content);

        Logger.info('Ollama API response received');
        return content.trim();
      } else {
        throw Exception('API Error: ${response.statusCode} - ${response.body}');
      }
    } catch (e) {
      Logger.error('Error calling Ollama API: $e');
      throw Exception('Failed to call AI service: $e');
    }
  }

  /// Legacy: Call Ollama API with single prompt (for backward compatibility)
  static Future<String> _callOllamaAPI(String prompt) async {
    return _callOllamaAPIWithMessages([
      {'role': 'system', 'content': _systemMessage},
      {'role': 'user', 'content': prompt},
    ]);
  }

  /// Create RAGResponse from AI response
  static RAGResponse _createRAGResponse({
    required String query,
    required String aiResponse,
    required TutorMode mode,
    required List<ContentChunk> sources,
  }) {
    try {
      // Create source citations
      final citations = sources
          .map(
            (chunk) => SourceCitation(
              contentId: chunk.contentId,
              chunkId: chunk.id,
              title: chunk.sourceDocument,
              concept: chunk.concept,
              subject: chunk.subject,
              excerpt: _getExcerpt(chunk.text),
              relevance: _calculateRelevance(query, chunk.text),
              pageNumber: chunk.pageNumber,
            ),
          )
          .toList();

      // Calculate confidence based on sources and response quality
      final confidence = _calculateResponseConfidence(aiResponse, sources);

      // Extract related concepts from sources
      final relatedConcepts = _extractRelatedConcepts(sources);

      return RAGResponse(
        answer: aiResponse,
        sources: citations,
        confidence: confidence,
        mode: mode,
        relatedConcepts: relatedConcepts,
        metadata: {
          'model': _model,
          'queryLength': query.length,
          'responseLength': aiResponse.length,
          'sourceCount': sources.length,
          'processingTime': DateTime.now().millisecondsSinceEpoch,
          'temperature': 0.7,
        },
      );
    } catch (e) {
      Logger.error('Error creating RAG response: $e');
      throw Exception('Failed to create RAG response: $e');
    }
  }

  /// Get excerpt from text
  static String _getExcerpt(String text, {int maxLength = 200}) {
    if (text.length <= maxLength) return text;
    return text.substring(0, maxLength - 3) + '...';
  }

  /// Calculate relevance score
  static double _calculateRelevance(String query, String text) {
    final queryWords = query.toLowerCase().split(' ');
    final textLower = text.toLowerCase();

    int matches = 0;
    for (final word in queryWords) {
      if (word.length > 2 && textLower.contains(word)) {
        matches++;
      }
    }

    return matches / queryWords.length;
  }

  /// Calculate response confidence
  static double _calculateResponseConfidence(
    String response,
    List<ContentChunk> sources,
  ) {
    double confidence = 0.0;

    // Base confidence on number of sources
    confidence += (sources.length / 5.0) * 0.4; // Max 0.4 for sources

    // Boost confidence if response mentions concepts from sources
    final sourceConcepts = sources.map((s) => s.concept.toLowerCase()).toSet();
    final responseLower = response.toLowerCase();

    int conceptMatches = 0;
    for (final concept in sourceConcepts) {
      if (responseLower.contains(concept)) {
        conceptMatches++;
      }
    }

    confidence +=
        (conceptMatches / sourceConcepts.length) *
        0.3; // Max 0.3 for concept matching

    // Boost confidence if response is substantial
    if (response.length > 100) {
      confidence += 0.2;
    }

    // Boost confidence if response cites sources
    if (responseLower.contains('fonte') ||
        responseLower.contains('conteúdo') ||
        responseLower.contains('material')) {
      confidence += 0.1;
    }

    return confidence.clamp(0.0, 1.0);
  }

  /// Extract related concepts
  static List<String> _extractRelatedConcepts(List<ContentChunk> sources) {
    final concepts = <String>{};

    for (final source in sources) {
      concepts.add(source.concept);
      if (source.subConcept != null) {
        concepts.add(source.subConcept!);
      }
    }

    return concepts.toList()..sort();
  }

  /// Create error response
  static RAGResponse _createErrorResponse(
    String query,
    TutorMode mode,
    String error,
  ) {
    return RAGResponse(
      answer:
          'Desculpe, ocorreu um erro ao processar sua pergunta: $error. Por favor, tente novamente mais tarde.',
      sources: [],
      confidence: 0.0,
      mode: mode,
      relatedConcepts: [],
      metadata: {'error': error, 'model': _model},
    );
  }

  /// Simple chat without RAG (for fallback)
  static Future<String> simpleChat(String message) async {
    try {
      Logger.info('Simple chat call');

      final url = Uri.parse(_baseUrl);

      final requestBody = {
        'model': _model,
        'messages': [
          {
            'role': 'user',
            'content': 'Responda de forma curta e direta: $message',
          },
        ],
        'stream': false,
        'options': {'temperature': 0.7, 'max_tokens': 500},
      };

      final response = await http
          .post(
            url,
            headers: {'Content-Type': 'application/json'},
            body: jsonEncode(requestBody),
          )
          .timeout(Duration(seconds: _timeoutSeconds));

      if (response.statusCode == 200) {
        final responseData = jsonDecode(response.body);
        final message = responseData['message'];
        final content = message?['content'] ?? '';

        return content.trim();
      } else {
        throw Exception('API Error: ${response.statusCode}');
      }
    } catch (e) {
      Logger.error('Error in simple chat: $e');
      return 'Desculpe, não consegui processar sua mensagem no momento.';
    }
  }

  /// Check if Ollama service is available
  static Future<bool> isServiceAvailable() async {
    try {
      Logger.info('Checking Ollama service availability');

      final url = Uri.parse('http://89.114.196.110:11434/api/tags');

      final response = await http.get(url).timeout(Duration(seconds: 10));

      if (response.statusCode == 200) {
        Logger.info('Ollama service available');
        return true;
      } else {
        Logger.warning(
          'Ollama service returned status: ${response.statusCode}',
        );
        return false;
      }
    } catch (e) {
      Logger.error('Ollama service not available: $e');
      return false;
    }
  }

  /// Get model information
  static Future<Map<String, dynamic>?> getModelInfo() async {
    try {
      final url = Uri.parse('http://89.114.196.110:11434/api/tags');

      final response = await http.get(url).timeout(Duration(seconds: 10));

      if (response.statusCode == 200) {
        final responseData = jsonDecode(response.body);
        final models = responseData['models'] as List? ?? [];

        for (final model in models) {
          if ((model['name'] as String? ?? '').contains('qwen3-coder')) {
            return model as Map<String, dynamic>;
          }
        }
      }

      return null;
    } catch (e) {
      Logger.error('Error getting model info: $e');
      return null;
    }
  }

  /// Remove LaTeX symbols from AI response
  static String _removeLaTeXSymbols(String text) {
    // Remove patterns like $$...$$ (display math)
    var cleaned = text.replaceAll(RegExp(r'\$\$[^$]*\$\$'), '');

    // Remove patterns like $...$ (inline math) - be more careful
    // Only remove when properly closed
    cleaned = cleaned.replaceAllMapped(
      RegExp(r'\$[^$\n]+?\$'),
      (match) => match.group(0)!.replaceAll(r'$', ''),
    );

    // Remove any remaining standalone $ symbols
    cleaned = cleaned.replaceAll(RegExp(r'(?<!\\)\$'), '');

    return cleaned;
  }

  /// Gerar quiz a partir de um prompt com contexto PDF embutido — sem histórico de conversa
  static Future<String> generateQuiz(
    String prompt, {
    bool isMathematics = false,
  }) async {
    final systemPrompt = isMathematics
        ? _getMathematicsSystemPrompt()
        : _getTextBasedSystemPrompt();

    final messages = <Map<String, String>>[
      {'role': 'system', 'content': systemPrompt},
      {'role': 'user', 'content': prompt},
    ];
    final raw = await _callOllamaAPIWithMessages(messages);

    // Filter out table questions for mathematics
    if (isMathematics) {
      return _filterTableQuestions(raw);
    }

    return raw;
  }

  /// Filter out questions that reference tables, graphs, or specific dates
  static String _filterTableQuestions(String json) {
    try {
      final List<dynamic> questions = jsonDecode(json);
      final List<Map<String, dynamic>> filtered = [];

      // Keywords that indicate table/graph dependence
      final tableKeywords = [
        'tabela',
        'gráfico',
        'dia 1/',
        'início de',
        'final de',
        'tendência',
        'evolução',
        'ao longo do tempo',
        'percentagem no início',
        'percentagem no final',
        'ano foi de',
        'dia específico',
        'data específica',
      ];

      for (final q in questions) {
        if (q is Map<String, dynamic>) {
          final questionText = (q['q'] as String? ?? '').toLowerCase();

          // Check if question contains table keywords
          final hasTableKeyword = tableKeywords.any(
            (keyword) => questionText.contains(keyword.toLowerCase()),
          );

          // Skip questions with table keywords
          if (!hasTableKeyword) {
            filtered.add(q);
          }
        }
      }

      // If filtered list is empty, return original to avoid empty response
      if (filtered.isEmpty) {
        Logger.warning(
          'All questions filtered out as table questions, returning original',
        );
        return json;
      }

      return jsonEncode(filtered);
    } catch (e) {
      Logger.error('Error filtering table questions: $e');
      return json;
    }
  }

  /// System prompt for mathematics quizzes
  static String _getMathematicsSystemPrompt() {
    return '''És um assistente educativo especializado em criar EXERCÍCIOS DE MATEMÁTICA.

REGRAS CRÍTICAS:
1. ANALISA TODO O CONTEÚDO fornecido:
   - Lê TODO o documento do início ao fim
   - Identifica TODOS os tópicos e tipos de exercícios presentes
   - NÃO te limites apenas aos primeiros exercícios
   - Cria perguntas sobre TODOS os tópicos encontrados no documento

2. MANTÉM o NÍVEL DE DIFICULDADE da ficha original:
   - Analisa a complexidade dos exercícios na ficha
   - Cria exercícios com o MESMO nível de dificuldade
   - NÃO faças perguntas mais avançadas do que o que está na ficha
   - Se a ficha tem exercícios simples, cria exercícios simples
   - Se a ficha tem exercícios complexos, cria exercícios complexos

3. ESTRICTAMENTE PROIBIDO criar perguntas que envolvam tabelas ou gráficos:
   - NUNCA faças perguntas que dependam de dados de tabelas
   - NUNCA faças perguntas que dependam de dados de gráficos
   - NUNCA faças perguntas sobre "tendência" ou "evolução ao longo do tempo"
   - NUNCA faças perguntas com datas específicas (ex: "dia 1/1/2017", "início de 2017")
   - NUNCA faças perguntas sobre "percentagem no início" ou "percentagem no final"
   - Se a ficha tem exercícios com tabelas, adapta-os para usar valores diretamente no texto
   - Fornece os dados necessários diretamente no texto da pergunta

4. Cria perguntas COMPLETAMENTE INDEPENDENTES:
   - CADA pergunta deve ser respondida SEM depender de outras perguntas
   - NUNCA faças referências à "pergunta anterior" ou "pergunta seguinte"
   - NUNCA uses resultados de perguntas anteriores em novas perguntas
   - CADA pergunta deve ter TODOS os dados necessários no seu enunciado
   - O aluno deve conseguir responder a qualquer pergunta independentemente da ordem

5. Usa VALORES DIFERENTES do conteúdo:
   - Os valores numéricos nas perguntas podem ser DIFERENTES dos que estão no PDF
   - Usa valores que mantenham o mesmo tipo de problema mas com números diferentes
   - Exemplo: se o PDF tem "um prisma de 5cm", podes usar "um prisma de 7cm"
   - O importante é manter a ESTRUTURA e TIPO de problema, não os valores exatos

6. EXEMPLOS DE PERGUNTAS PROIBIDAS (NÃO faças estas):
   - "Qual é a percentagem da área afetada pela vespa no início do dia 1/1/2017?" (PROIBIDO - depende de tabela)
   - "Com o passar do tempo, a área afetada tende para:" (PROIBIDO - depende de gráfico/evolução)
   - "Em que ano a produção foi de X toneladas?" (PROIBIDO - depende de tabela)
   - "Usando o resultado da pergunta anterior, calcule..." (PROIBIDO - depende de pergunta anterior)
   - "Considerando o valor calculado acima, determine..." (PROIBIDO - depende de pergunta anterior)

7. EXEMPLOS DE PERGUNTAS PERMITIDAS (faz estas):
   - "Um prisma tem base quadrada com 5cm de lado e altura 12cm. Qual é o volume?" (PERMITIDO - dados diretos, independente)
   - "Calcule a área de um círculo com raio 7cm." (PERMITIDO - dados diretos, independente)
   - "Resolva a equação 2x + 5 = 15." (PERMITIDO - dados diretos, independente)
   - "Uma esfera tem raio de 3 metros. Determine o seu volume." (PERMITIDO - dados diretos, independente)

8. ANALISA OS EXEMPLOS DE EXERCÍCIOS no contexto fornecido:
   - Identifica os TIPOS de problemas que aparecem na ficha (ex: determinar volume de sólidos, planos que decompõem prismas, equações, frações, etc.)
   - Repara na COMPLEXIDADE e estrutura dos exercícios originais
   - Gera exercícios NOVOS que seguem a MESMA estrutura e complexidade mas com VALORES DIFERENTES

9. NÃO copies perguntas do conteúdo fornecido
10. Usa valores diferentes mas mantém o TIPO e ESTRUTURA do problema

11. INCLUI TODOS OS DADOS NECESSÁRIOS no texto da pergunta:
    - Fornece TODOS os valores numéricos necessários
    - Exemplo: "Um prisma tem base quadrada com 5cm de lado e altura 12cm. Qual é o volume?"
    - NUNCA faças perguntas que dependam de dados que não forneces no texto

12. Na explicação, usa texto normal, SEM LaTeX:
    - Escreve "a fração é 15/44" em vez de "\\frac{15}{44}"
    - Escreve "raiz quadrada de 25" em vez de "\\sqrt{25}"
    - Escreve "x ao quadrado" em vez de "x^2"
    - Apenas usa LaTeX na própria pergunta se for estritamente necessário para a notação matemática

13. Cria exercícios variados sobre TODOS os tópicos:
    - Diferentes valores numéricos
    - Diferentes contextos quando aplicável
    - Mesmo TIPO e complexidade de problema matemático
    - VARIADOS entre todos os tópicos do documento

14. EXEMPLOS DE TIPOS DE EXERCÍCIOS DE MATEMÁTICA:
    - Determinar volumes de sólidos (prismas, pirâmides, cilindros, cones, esferas)
    - Planos que decompõem sólidos em partes geometricamente iguais
    - Equações lineares e quadráticas
    - Sistemas de equações
    - Funções e gráficos
    - Geometria analítica
    - Trigonometria
    - Probabilidade e estatística
    - Cálculo de áreas e perímetros
    - Frações e números racionais
    - Potências e raízes

FORMATO JSON:
[{"q":"Pergunta com dados completos incluídos","opts":["A) opção","B) opção","C) opção","D) opção"],"ans":0,"exp":"Explicação em texto normal sem LaTeX"}]
ans é o índice (0-3) da opção correcta.''';
  }

  /// System prompt for text-based subject quizzes
  static String _getTextBasedSystemPrompt() {
    return '''És um assistente educativo especializado em criar quizzes pedagógicos.

REGRAS CRÍTICAS:
1. Cria perguntas de compreensão, análise e síntese
2. Baseia-te nos conceitos e temas do conteúdo
3. Evita perguntas de cópia direta
4. Foca em entender e aplicar os conceitos

5. INCLUI CONTEXTO SUFICIENTE EM CADA PERGUNTA:
   - Cada pergunta deve ser compreensível por si só
   - Fornece contexto necessário sobre o assunto da pergunta
   - Exemplo PROIBIDO: "Em que ano a vespa asiática afetou mais de 50% da área?" (sem contexto)
   - Exemplo PERMITIDO: "De acordo com o estudo sobre a vespa asiática em Portugal, em que ano esta espécie afetou mais de 50% da área de distribuição?"
   - Exemplo PROIBIDO: "Qual foi a principal causa?" (sem contexto)
   - Exemplo PERMITIDO: "Qual foi a principal causa da extinção do dodo, segundo o texto?"

6. EVITA perguntas que dependam de dados específicos não mencionados:
   - NUNCA faças perguntas sobre "ano X" ou "data X" sem especificar de que ano/data se trata
   - NUNCA faças perguntas sobre "porcentagem X" sem explicar o contexto
   - NUNCA faças perguntas que dependam de tabelas ou gráficos

7. EXEMPLOS DE PERGUNTAS BEM FORMULADAS:
   - "De acordo com o texto sobre a vespa asiática, qual é o principal impacto desta espécie na biodiversidade portuguesa?"
   - "O estudo sobre a mudança climática menciona que a temperatura média aumentou. Qual foi a principal causa mencionada?"
   - "Segundo o documento sobre a história de Portugal, qual foi o resultado da Batalha de Aljubarrota?"

FORMATO JSON:
[{"q":"Pergunta com contexto suficiente","opts":["A) opção","B) opção","C) opção","D) opção"],"ans":0,"exp":"Explicação"}]
ans é o índice (0-3) da opção correcta.''';
  }

  /// Test the service with a simple query
  static Future<String> testService() async {
    try {
      final testQuery = 'Olá, você está funcionando?';
      final response = await simpleChat(testQuery);

      return 'Service test successful: $response';
    } catch (e) {
      return 'Service test failed: $e';
    }
  }

  /// Cache do último contexto PDF enviado ao modelo — reutilizado em follow-ups
  static String _lastPdfContext = '';

  /// Limpar contexto cacheado — chamar ao mudar de material
  static void clearLastContext() {
    _lastPdfContext = '';
    Logger.info('Last PDF context cleared');
  }

  /// Detecta se a query é small talk (saudação, conversa casual) — sem necessidade de contexto PDF
  static bool _isSmallTalk(String query) {
    final q = query.trim().toLowerCase();
    const triggers = [
      'olá',
      'ola',
      'oi',
      'ei',
      'hey',
      'hi',
      'tudo bem',
      'tudo bom',
      'como estás',
      'como estas',
      'como vai',
      'bom dia',
      'boa tarde',
      'boa noite',
      'obrigado',
      'obrigada',
      'muito obrigado',
      'muito obrigada',
      'valeu',
      'ok',
      'okay',
      'fixe',
      'ótimo',
      'otimo',
      'perfeito',
      'excelente',
      'adeus',
      'até logo',
      'até mais',
      'tchau',
      'quem és',
      'quem es',
      'quem é o vico',
      'o que és',
      'o que fazes',
      'apresenta-te',
      'apresentate',
    ];
    // Exact match or starts with a trigger phrase
    if (triggers.any(
      (t) => q == t || q.startsWith('$t ') || q.startsWith('$t,'),
    )) {
      return true;
    }
    // Very short messages with no educational keywords
    final words = q.split(RegExp(r'\s+'));
    if (words.length <= 3) {
      const eduKeywords = [
        'explica',
        'define',
        'o que é',
        'como funciona',
        'porque',
        'fórmula',
        'formula',
        'exemplo',
        'exercício',
        'exercicio',
        'matéria',
        'materia',
        'tema',
        'conceito',
        'resumo',
      ];
      if (!eduKeywords.any((k) => q.contains(k))) return true;
    }
    return false;
  }

  /// Detecta se a query é um follow-up (pergunta curta/vaga sem keywords de conteúdo)
  static bool _isFollowUp(String query) {
    final q = query.trim().toLowerCase();
    // Menos de 6 palavras E começa com pronome/advérbio de follow-up
    final words = q.split(RegExp(r'\s+'));
    if (words.length > 8) return false;
    const followUpStarters = [
      'e ',
      'e o',
      'e a',
      'e os',
      'e as',
      'mas ',
      'então ',
      'explica',
      'explique',
      'explica melhor',
      'melhor',
      'mais detalhes',
      'podes',
      'pode ',
      'consegues',
      'e se ',
      'e quando',
      'dá um exemplo',
      'da um exemplo',
      'um exemplo',
      'exemplo',
      'como assim',
      'o que significa',
      'porquê',
      'porque isso',
      'e o ponto',
      'e a regra',
      'continua',
      'continua',
      'o que mais',
      'mais algum',
      'e depois',
      'e agora',
    ];
    return followUpStarters.any((s) => q.startsWith(s) || q == s.trim());
  }

  /// Build dynamic system prompt based on selected materials and discipline
  static String _buildSystemPrompt({
    List<String>? selectedMaterialNames,
    String? disciplineName,
    bool isMathematics = false,
  }) {
    final buffer = StringBuffer();

    buffer.writeln(
      r'''Tu és "Vico", o Assistente IA oficial do Learn It — uma plataforma educativa portuguesa.''',
    );
    buffer.writeln();
    buffer.writeln('Nunca referes o nome do modelo de linguagem.');
    buffer.writeln('Nunca dizes que és Qwen, OpenAI ou qualquer outro modelo.');
    buffer.writeln('Respondes sempre como o Vico.');
    buffer.writeln();
    buffer.writeln('Tens personalidade simpática, confiante e motivadora.');
    buffer.writeln(
      'Podes responder normalmente a saudações, agradecimentos e conversa casual — sê natural e amigável.',
    );
    buffer.writeln();

    // Material context section
    if (disciplineName != null && disciplineName.isNotEmpty) {
      buffer.writeln('DISCIPLINA ATUAL: $disciplineName');
    }
    if (selectedMaterialNames != null && selectedMaterialNames.isNotEmpty) {
      buffer.writeln(
        'MATERIAIS SELECIONADOS: ${selectedMaterialNames.join(", ")}',
      );
    }
    if (disciplineName != null ||
        (selectedMaterialNames != null && selectedMaterialNames.isNotEmpty)) {
      buffer.writeln();
    }

    // LaTeX prohibition (always)
    buffer.writeln(
      'IMPORTANTE: NUNCA uses LaTeX ou símbolos como \$ ou \$\$ para fórmulas matemáticas.',
    );
    buffer.writeln(
      'Usa apenas texto normal e caracteres Unicode para símbolos matemáticos (ex: x², ³, ¹⁄², π, √).',
    );
    buffer.writeln();

    // Discipline-specific rules
    if (isMathematics) {
      buffer.writeln('REGRAS CRÍTICAS PARA MATEMÁTICA:');
      buffer.writeln(
        '- O material fornecido serve como REFERÊNCIA de matéria, fórmulas e métodos.',
      );
      buffer.writeln(
        '- Podes CRIAR exercícios NOVOS que sigam a mesma lógica, fórmulas e métodos do material.',
      );
      buffer.writeln(
        '- NÃO copies exercícios diretamente do material — cria variações com valores diferentes.',
      );
      buffer.writeln(
        '- Se o aluno pedir exercícios, cria exercícios novos baseados nos CONCEITOS e FÓRMULAS do material.',
      );
      buffer.writeln(
        '- Se a resposta não estiver no contexto, usa o teu conhecimento matemático geral para ajudar, mas prioriza os métodos do material.',
      );
      buffer.writeln(
        '- SEMPRE fornece o passo a passo completo com o resultado final.',
      );
    } else {
      buffer.writeln('REGRAS CRÍTICAS PARA PERGUNTAS EDUCATIVAS:');
      buffer.writeln(
        '- Quando te for fornecido contexto de materiais do professor (indicado com [MATERIAL: ...]), responde EXCLUSIVAMENTE com base nesse conteúdo.',
      );
      buffer.writeln(
        '- NÃO inventes factos educativos, NÃO uses conhecimento externo sobre matérias escolares.',
      );
      buffer.writeln(
        '- Se a resposta educativa não estiver no contexto fornecido, diz claramente: "Não encontrei essa informação no material disponível."',
      );
    }
    buffer.writeln(
      '- Para conversa casual e saudações não precisas de contexto — responde livremente com a tua personalidade.',
    );
    buffer.writeln();

    // Material handling rules (always)
    buffer.writeln('IMPORTANTE - COMO TRATAR MATERIAIS SELECIONADOS:');
    buffer.writeln(
      '- Quando o aluno selecionar materiais (PDFs, fichas, exames), ASSUME que o aluno quer ajuda com esses materiais.',
    );
    buffer.writeln(
      '- NUNCA perguntes "que ficha pretendo resolver" ou "o que pretendo resolver".',
    );
    buffer.writeln(
      '- NUNCA perguntes "em que posso ajudar" quando materiais estão selecionados.',
    );
    buffer.writeln(
      '- ASSUME automaticamente que o aluno quer explicação, resolução ou ajuda com os materiais selecionados.',
    );
    if (selectedMaterialNames != null && selectedMaterialNames.isNotEmpty) {
      buffer.writeln(
        '- Se a pergunta do aluno for vaga (ex: "ajuda"), oferece ajuda específica sobre os materiais selecionados.',
      );
    }
    buffer.writeln();

    // Greeting rules (always)
    buffer.writeln('IMPORTANTE - NÃO REPITAS SAUDAÇÕES:');
    buffer.writeln(
      '- NUNCA começes uma resposta com "Olá", "Olá de novo", "Oi", "Bom dia", etc., a menos que seja a PRIMEIRA mensagem da conversa.',
    );
    buffer.writeln(
      '- Nas respostas subsequentes, vai DIRETO ao assunto sem saudar novamente.',
    );
    buffer.writeln(
      '- Se já saudaste o utilizador uma vez na conversa, NUNCA o faças de novo.',
    );
    buffer.writeln();

    // Complete response rules (always)
    buffer.writeln('IMPORTANTE - RESPOSTAS COMPLETAS:');
    buffer.writeln('- NUNCA termines respostas com dois pontos (:).');
    buffer.writeln(
      '- NUNCA deixes respostas incompletas como "A função é: " ou "Calculamos o denominador: ".',
    );
    buffer.writeln(
      '- SEMPRE completa as frases e fornece a resposta completa.',
    );
    buffer.writeln(
      '- Se precisares de explicar um cálculo, explica-o completamente com o resultado final.',
    );
    buffer.writeln(
      '- Se precisares de definir algo, fornece a definição completa.',
    );

    return buffer.toString();
  }

  /// Simple ask method for chat UI - uses conversation memory, teacher PDFs, and Vico identity
  /// [selectedMaterialIds] — se fornecido, limita o RAG apenas aos materiais escolhidos pelo aluno
  /// [selectedMaterialNames] — nomes dos materiais selecionados (para contextualizar a IA)
  /// [disciplineName] — nome da disciplina (ex: "Matemática A", "Português")
  /// [isMathematics] — true se for matemática ou disciplina quantitativa
  static Future<String> ask(
    String userQuery, {
    List<String>? selectedMaterialIds,
    List<String>? selectedMaterialNames,
    String? disciplineName,
    bool isMathematics = false,
  }) async {
    Logger.info('USING RAG AI SERVICE');

    // PASSO 1 — Criar a lista messages vazia
    List<Map<String, String>> messages = [];

    // PASSO 2 — ADICIONAR SYSTEM MESSAGE DO VICO (SEMPRE PRIMEIRO)
    final systemPrompt = _buildSystemPrompt(
      selectedMaterialNames: selectedMaterialNames,
      disciplineName: disciplineName,
      isMathematics: isMathematics,
    );
    messages.add({'role': 'system', 'content': systemPrompt});

    // PASSO 3 — BUSCAR MEMÓRIA DA CONVERSA NA Cloud Firestore (máx 10 para manter contexto)
    final conversationId = ChatMemoryService.currentConversationId;
    var lastHistoryMessageIsDuplicate = false;
    if (conversationId != null) {
      final conversationHistory =
          await ChatMemoryService.getConversationMessages(
            conversationId: conversationId,
            limit: 10,
          );
      for (final msg in conversationHistory) {
        messages.add({
          'role': msg['role'] as String,
          'content': msg['content'] as String,
        });
      }
      // Verificar se a última mensagem do histórico já é a pergunta atual
      // (evita duplicação quando a UI guardou a mensagem antes de chamar ask())
      if (conversationHistory.isNotEmpty) {
        final lastMsg = conversationHistory.last;
        if (lastMsg['role'] == 'user' && lastMsg['content'] == userQuery) {
          lastHistoryMessageIsDuplicate = true;
          Logger.info(
            'Last history message matches current query — skipping duplicate user message',
          );
        }
      }
    }

    // Log de confirmação de ordem do histórico
    if (messages.length > 1) {
      Logger.info('History order fixed. First message: ${messages[1]}');
    }

    // PASSO 4 — BUSCAR PDFs DO PROFESSOR NO Firebase Storage (RAG CHUNK RETRIEVAL)
    // Small talk: skip PDF lookup entirely and go straight to model
    if (_isSmallTalk(userQuery)) {
      Logger.info('Small talk detected — skipping PDF lookup');
      if (!lastHistoryMessageIsDuplicate) {
        messages.add({'role': 'user', 'content': userQuery});
      }
      // Only save to Firestore if not already saved by UI
      if (!lastHistoryMessageIsDuplicate) {
        await ChatMemoryService.saveMessage(role: 'user', content: userQuery);
      }
      final response = await _callOllamaAPIWithMessages(messages);
      await ChatMemoryService.saveMessage(role: 'assistant', content: response);
      return response;
    }
    // Detectar follow-up e reutilizar contexto anterior se disponível
    String pdfContext;
    if (_isFollowUp(userQuery) && _lastPdfContext.isNotEmpty) {
      pdfContext = _lastPdfContext;
      Logger.info(
        'Follow-up detected — reusing last PDF context (${pdfContext.length} chars)',
      );
    } else {
      pdfContext = await MaterialsRAGService.getRelevantChunks(
        userQuery: userQuery,
        maxMaterials: 10,
        maxChunks: 20,
        selectedMaterialIds: selectedMaterialIds,
      );
      if (pdfContext.isNotEmpty) {
        _lastPdfContext = pdfContext;
        Logger.info(
          'PDF context sent to model (${pdfContext.length} chars): ${pdfContext.length > 300 ? pdfContext.substring(0, 300) : pdfContext}',
        );
      }
    }
    if (pdfContext.isEmpty) {
      // Sem contexto encontrado — responder com base na personalidade mas sem inventar conteúdo
      if (!lastHistoryMessageIsDuplicate) {
        messages.add({'role': 'user', 'content': userQuery});
      }
      // Only save to Firestore if not already saved by UI
      if (!lastHistoryMessageIsDuplicate) {
        await ChatMemoryService.saveMessage(role: 'user', content: userQuery);
      }
      final response = await _callOllamaAPIWithMessages(messages);
      await ChatMemoryService.saveMessage(role: 'assistant', content: response);
      return response;
    }

    // PASSO 5 — adicionar a pergunta do user (com contexto embutido se disponível)
    final userContent = pdfContext.isNotEmpty
        ? '''Usa APENAS o seguinte contexto para responder. Não uses conhecimento externo.
Se a resposta não estiver no contexto, diz: "Não encontrei essa informação no material disponível."

$pdfContext

Pergunta: $userQuery'''
        : userQuery;
    if (!lastHistoryMessageIsDuplicate) {
      messages.add({'role': 'user', 'content': userContent});
    }

    Logger.info(
      'USING RAG AI SERVICE - Built messages array with ${messages.length} messages',
    );

    // Only save to Firestore if not already saved by UI
    if (!lastHistoryMessageIsDuplicate) {
      await ChatMemoryService.saveMessage(role: 'user', content: userQuery);
    }

    // Call API
    final response = await _callOllamaAPIWithMessages(messages);

    // Save AI response to memory
    await ChatMemoryService.saveMessage(role: 'assistant', content: response);

    return response;
  }
}
