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': '''Tu és "Vico", o Assistente IA oficial do Teach 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.''',
      });

      // PASSO 3 — BUSCAR MEMÓRIA DA CONVERSA NA Cloud Firestore
      final conversationHistory = await ChatMemoryService.getRecentMessages(
        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: 5,
        maxChunks: 5,
      );
      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 =
      '''Tu és "Vico", o Assistente IA oficial do Teach 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.''';

  /// 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'];
        final content = message?['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) {
        final responseData = jsonDecode(response.body);
        final models = responseData['models'] as List? ?? [];

        final hasModel = models.any(
          (model) => (model['name'] as String? ?? '').contains('qwen3-coder'),
        );

        Logger.info('Ollama service available, model found: $hasModel');
        return hasModel;
      } 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;
    }
  }

  /// Gerar quiz a partir de um prompt com contexto PDF embutido — sem histórico de conversa
  static Future<String> generateQuiz(String prompt) async {
    final messages = <Map<String, String>>[
      {
        'role': 'system',
        'content':
            'És um assistente educativo especializado em criar quizzes pedagógicos. '
            'Cria sempre perguntas claras, baseadas exclusivamente no contexto fornecido.',
      },
      {'role': 'user', 'content': prompt},
    ];
    return await _callOllamaAPIWithMessages(messages);
  }

  /// 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());
  }

  /// 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
  static Future<String> ask(
    String userQuery, {
    List<String>? selectedMaterialIds,
  }) 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)
    messages.add({
      'role': 'system',
      'content':
          '''Tu és "Vico", o Assistente IA oficial do Teach it — uma plataforma educativa portuguesa.

Nunca referes o nome do modelo de linguagem.
Nunca dizes que és Qwen, OpenAI ou qualquer outro modelo.
Respondes sempre como o Vico.

Tens personalidade simpática, confiante e motivadora.
Podes responder normalmente a saudações, agradecimentos e conversa casual — sê natural e amigável.

REGRAS CRÍTICAS PARA PERGUNTAS EDUCATIVAS:
- Quando te for fornecido contexto de materiais do professor (indicado com [MATERIAL: ...]), responde EXCLUSIVAMENTE com base nesse conteúdo.
- NÃO inventes factos educativos, NÃO uses conhecimento externo sobre matérias escolares.
- Se a resposta educativa não estiver no contexto fornecido, diz claramente: "Não encontrei essa informação no material disponível."
- Para conversa casual e saudações não precisas de contexto — responde livremente com a tua personalidade.''',
    });

    // PASSO 3 — BUSCAR MEMÓRIA DA CONVERSA NA Cloud Firestore (máx 4 para poupar heap)
    final conversationHistory = await ChatMemoryService.getRecentMessages(
      limit: 4,
    );
    for (final msg in conversationHistory) {
      messages.add({
        'role': msg['role'] as String,
        'content': msg['content'] as String,
      });
    }

    // 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');
      messages.add({'role': 'user', 'content': userQuery});
      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: 5,
        maxChunks: 5,
        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
      messages.add({'role': 'user', 'content': userQuery});
      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;
    messages.add({'role': 'user', 'content': userContent});

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

    // Save user message to Firestore
    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;
  }
}
