using System.Text.Json;
using Mindforge.API.Exceptions;
using Mindforge.API.Models.Flashcards;
using Mindforge.API.Models.Requests;
using Mindforge.API.Services.Interfaces;

namespace Mindforge.API.Services
{
    public class FlashcardService : IFlashcardService
    {
        private readonly IAgentService _agentService;
        private readonly IGiteaService _giteaService;
        private readonly IFlashcardRepository _flashcardRepository;
        private readonly ILogger<FlashcardService> _logger;
        private static readonly JsonSerializerOptions JsonOptions = new()
        {
            PropertyNameCaseInsensitive = true
        };

        public FlashcardService(
            IAgentService agentService,
            IGiteaService giteaService,
            IFlashcardRepository flashcardRepository,
            ILogger<FlashcardService> logger)
        {
            _agentService = agentService;
            _giteaService = giteaService;
            _flashcardRepository = flashcardRepository;
            _logger = logger;
        }

        public async Task<IReadOnlyList<FlashcardLibraryDetails>> GenerateFlashcardsAsync(FlashcardGenerateRequest request)
        {
            if (request.FilePaths.Count == 0)
            {
                throw new UserException("Selecione ao menos um arquivo para gerar flashcards.");
            }

            if (request.Amount is < 10 or > 50)
            {
                throw new UserException("A quantidade de flashcards deve estar entre 10 e 50 por arquivo.");
            }

            var uniqueFilePaths = request.FilePaths
                .Where(path => !string.IsNullOrWhiteSpace(path))
                .Select(path => path.Trim())
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .ToList();

            if (uniqueFilePaths.Count == 0)
            {
                throw new UserException("Nenhum caminho de arquivo valido foi enviado.");
            }

            var generatedLibraries = new List<FlashcardLibraryDetails>();
            foreach (var filePath in uniqueFilePaths)
            {
                var fileContent = await _giteaService.GetFileContentAsync(filePath);
                var fileName = Path.GetFileName(filePath);
                var subject = ExtractSubject(filePath);
                var cards = await GenerateCardsForFileAsync(filePath, fileContent, request.Amount, request.Difficulty);

                var library = await _flashcardRepository.UpsertLibraryAsync(
                    filePath,
                    fileName,
                    subject,
                    request.Difficulty.ToString(),
                    cards);

                generatedLibraries.Add(library);
            }

            return generatedLibraries;
        }

        public Task<IReadOnlyList<FlashcardLibrarySummary>> GetLibrariesAsync()
        {
            return _flashcardRepository.GetLibrariesAsync();
        }

        public Task<FlashcardLibraryDetails?> GetLibraryByIdAsync(long libraryId)
        {
            return _flashcardRepository.GetLibraryByIdAsync(libraryId);
        }

        public async Task<IReadOnlyList<FlashcardCard>> BuildReviewSessionAsync(FlashcardReviewSessionRequest request)
        {
            var libraryIds = (request.LibraryIds ?? [])
                .Where(id => id > 0)
                .Distinct()
                .ToList();

            if (libraryIds.Count == 0)
            {
                throw new UserException("Selecione ao menos uma biblioteca para iniciar a revisao.");
            }

            return await _flashcardRepository.GetCardsForLibrariesAsync(libraryIds);
        }

        public async Task<FlashcardRagDashboard> GetRagStatusAsync()
        {
            var now = DateTime.UtcNow;
            var cards = await _flashcardRepository.GetAllCardsWithLibraryAsync();

            var ragLibraries = cards
                .GroupBy(card => card.LibraryId)
                .Select(group => BuildRagLibrary(group.ToList(), now))
                .ToList();

            var subjectGroups = ragLibraries
                .GroupBy(library => library.Subject, StringComparer.OrdinalIgnoreCase)
                .OrderBy(group => group.Key, StringComparer.OrdinalIgnoreCase)
                .Select(subjectGroup =>
                {
                    var subSubjectGroups = subjectGroup
                        .GroupBy(library => library.SubSubject, StringComparer.OrdinalIgnoreCase)
                        .OrderBy(group => group.Key, StringComparer.OrdinalIgnoreCase)
                        .Select(subSubjectGroup =>
                        {
                            var subSubjectLibraries = subSubjectGroup
                                .OrderBy(library => StatusSortOrder(library.RagStatus))
                                .ThenBy(library => library.FileName, StringComparer.OrdinalIgnoreCase)
                                .ToList();

                            return new FlashcardRagSubSubjectGroup
                            {
                                SubSubject = subSubjectGroup.Key,
                                Summary = BuildSummary(subSubjectLibraries),
                                Libraries = subSubjectLibraries
                            };
                        })
                        .ToList();

                    return new FlashcardRagSubjectGroup
                    {
                        Subject = subjectGroup.Key,
                        Summary = BuildSummary(subjectGroup),
                        SubSubjects = subSubjectGroups
                    };
                })
                .ToList();

            return new FlashcardRagDashboard
            {
                GeneratedAt = now,
                Subjects = subjectGroups
            };
        }

        public async Task RecordReviewAnswerAsync(long cardId, bool correct)
        {
            if (cardId <= 0)
            {
                throw new UserException("Card de revisao invalido.");
            }

            await _flashcardRepository.RecordReviewAnswerAsync(cardId, correct);
        }

        private async Task<IReadOnlyList<FlashcardDraftCard>> GenerateCardsForFileAsync(
            string filePath,
            string fileContent,
            int amount,
            FlashcardDifficulty difficulty)
        {
            var difficultyInstruction = difficulty switch
            {
                FlashcardDifficulty.Medium => "Crie perguntas de nivel intermediario, combinando conceitos e exigindo raciocínio moderado.",
                _ => "Crie perguntas diretas e objetivas para facilitar memorizacao inicial."
            };

            var systemPrompt =
                "Voce e um assistente educacional especializado em flashcards.\n"
                + $"Gere exatamente {amount} flashcards em Portugues do Brasil.\n"
                + "Cada item precisa conter:\n"
                + "- front: pergunta ou gatilho curto\n"
                + "- back: resposta clara, correta e objetiva\n"
                + $"{difficultyInstruction}\n"
                + "Responda apenas com JSON valido, sem markdown, no formato:\n"
                + "{\n"
                + "  \"flashcards\": [\n"
                + "    { \"front\": \"texto\", \"back\": \"texto\" }\n"
                + "  ]\n"
                + "}";

            var userPrompt = $"""
                Arquivo: {filePath}
                Conteudo:
                {fileContent}
                """;

            var rawResult = await _agentService.ProcessRequestAsync(systemPrompt, userPrompt);
            var cards = ParseFlashcardsFromJson(rawResult);

            if (cards.Count < amount)
            {
                throw new Exception($"Quantidade insuficiente de flashcards gerados para {filePath}. Esperado: {amount}. Recebido: {cards.Count}.");
            }

            if (cards.Count > amount)
            {
                _logger.LogWarning(
                    "Quantidade de flashcards acima do solicitado para {FilePath}. Solicitado: {Amount}. Recebido: {Generated}.",
                    filePath,
                    amount,
                    cards.Count);
                cards = cards.Take(amount).ToList();
            }

            for (var i = 0; i < cards.Count; i++)
            {
                cards[i].Position = i + 1;
            }

            return cards;
        }

        private static List<FlashcardDraftCard> ParseFlashcardsFromJson(string rawResult)
        {
            var cleanJson = StripJsonCodeBlock(rawResult);

            var payload = JsonSerializer.Deserialize<FlashcardJsonPayload>(cleanJson, JsonOptions);
            if (payload?.Flashcards is { Count: > 0 })
            {
                return payload.Flashcards
                    .Where(card => !string.IsNullOrWhiteSpace(card.Front) && !string.IsNullOrWhiteSpace(card.Back))
                    .Select(card => new FlashcardDraftCard
                    {
                        Front = card.Front.Trim(),
                        Back = card.Back.Trim()
                    })
                    .ToList();
            }

            var directList = JsonSerializer.Deserialize<List<FlashcardJsonCard>>(cleanJson, JsonOptions);
            if (directList is { Count: > 0 })
            {
                return directList
                    .Where(card => !string.IsNullOrWhiteSpace(card.Front) && !string.IsNullOrWhiteSpace(card.Back))
                    .Select(card => new FlashcardDraftCard
                    {
                        Front = card.Front.Trim(),
                        Back = card.Back.Trim()
                    })
                    .ToList();
            }

            throw new Exception("Resposta de flashcards invalida. O modelo nao retornou JSON estruturado.");
        }

        private static string StripJsonCodeBlock(string value)
        {
            var trimmed = value.Trim();
            if (!trimmed.StartsWith("```", StringComparison.Ordinal))
            {
                return trimmed;
            }

            var lines = trimmed.Split('\n');
            if (lines.Length <= 2)
            {
                return trimmed.Trim('`', '\r', '\n');
            }

            return string.Join('\n', lines.Skip(1).Take(lines.Length - 2)).Trim();
        }

        private static string ExtractSubject(string filePath)
        {
            var normalized = filePath.Replace('\\', '/');
            var segments = normalized.Split('/', StringSplitOptions.RemoveEmptyEntries);
            if (segments.Length == 0)
            {
                return "Geral";
            }

            var concursosIndex = Array.FindIndex(
                segments,
                segment => segment.Equals("concursos", StringComparison.OrdinalIgnoreCase)
                    || segment.Equals("concurso", StringComparison.OrdinalIgnoreCase));

            if (concursosIndex >= 0 && concursosIndex + 1 < segments.Length)
            {
                return segments[concursosIndex + 1];
            }

            return segments[0];
        }

        private static string ExtractSubSubject(string filePath)
        {
            var normalized = filePath.Replace('\\', '/');
            var segments = normalized.Split('/', StringSplitOptions.RemoveEmptyEntries);
            if (segments.Length <= 2)
            {
                return "Geral";
            }

            var concursosIndex = Array.FindIndex(
                segments,
                segment => segment.Equals("concursos", StringComparison.OrdinalIgnoreCase)
                    || segment.Equals("concurso", StringComparison.OrdinalIgnoreCase));

            var subjectIndex = concursosIndex >= 0 && concursosIndex + 1 < segments.Length
                ? concursosIndex + 1
                : 0;

            var subSubjectStart = subjectIndex + 1;
            var subSubjectEnd = segments.Length - 1;

            if (subSubjectStart >= subSubjectEnd)
            {
                return "Geral";
            }

            return string.Join(" / ", segments[subSubjectStart..subSubjectEnd]);
        }

        private static FlashcardRagLibrary BuildRagLibrary(
            IReadOnlyList<FlashcardCardWithLibrary> cards,
            DateTime referenceTime)
        {
            var firstCard = cards[0];
            var subject = string.IsNullOrWhiteSpace(firstCard.Subject)
                ? ExtractSubject(firstCard.FilePath)
                : firstCard.Subject;
            var subSubject = ExtractSubSubject(firstCard.FilePath);
            var correctCount = cards.Sum(card => card.CorrectCount);
            var incorrectCount = cards.Sum(card => card.IncorrectCount);
            var totalAnswers = correctCount + incorrectCount;
            var lastReviewedAt = cards.Max(card => card.LastReviewedAt);
            var performanceRate = totalAnswers == 0
                ? 0
                : (double)correctCount / totalAnswers;

            return new FlashcardRagLibrary
            {
                LibraryId = firstCard.LibraryId,
                FilePath = firstCard.FilePath,
                FileName = firstCard.FileName,
                Subject = subject,
                SubSubject = subSubject,
                CorrectCount = correctCount,
                IncorrectCount = incorrectCount,
                CardCount = cards.Count,
                TotalAnswers = totalAnswers,
                PerformanceRate = performanceRate,
                LastReviewedAt = lastReviewedAt,
                RagStatus = DetermineRagStatus(lastReviewedAt, performanceRate, referenceTime)
            };
        }

        private static string DetermineRagStatus(DateTime? lastReviewedAt, double performanceRate, DateTime referenceTime)
        {
            if (!lastReviewedAt.HasValue)
            {
                return "Grey";
            }

            var lastReviewUtc = lastReviewedAt.Value.ToUniversalTime();
            var elapsedDays = (referenceTime.Date - lastReviewUtc.Date).TotalDays;

            if (elapsedDays >= 40 || performanceRate < 0.4)
            {
                return "Red";
            }

            if (elapsedDays >= 30 || performanceRate <= 0.6)
            {
                return "Amber";
            }

            if (elapsedDays < 30 && performanceRate > 0.6)
            {
                return "Green";
            }

            return "Amber";
        }

        private static FlashcardRagSummary BuildSummary(IEnumerable<FlashcardRagLibrary> libraries)
        {
            var libraryList = libraries.ToList();
            var greenCount = libraryList.Count(library => library.RagStatus == "Green");
            var amberCount = libraryList.Count(library => library.RagStatus == "Amber");
            var redCount = libraryList.Count(library => library.RagStatus == "Red");
            var greyCount = libraryList.Count(library => library.RagStatus == "Grey");
            var activeCount = greenCount + amberCount + redCount;

            var greenPercentage = activeCount == 0
                ? 0
                : Math.Round((double)greenCount / activeCount * 100, 2);
            var attentionPercentage = activeCount == 0
                ? 0
                : Math.Round((double)(amberCount + redCount) / activeCount * 100, 2);

            return new FlashcardRagSummary
            {
                GreenCount = greenCount,
                AmberCount = amberCount,
                RedCount = redCount,
                GreyCount = greyCount,
                ActiveCount = activeCount,
                GreenPercentage = greenPercentage,
                AttentionPercentage = attentionPercentage
            };
        }

        private static int StatusSortOrder(string status)
        {
            return status switch
            {
                "Red" => 0,
                "Amber" => 1,
                "Green" => 2,
                _ => 3
            };
        }

        private class FlashcardJsonPayload
        {
            public List<FlashcardJsonCard> Flashcards { get; set; } = [];
        }

        private class FlashcardJsonCard
        {
            public string Front { get; set; } = string.Empty;
            public string Back { get; set; } = string.Empty;
        }
    }
}
