using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FluentResults;
using MediatR;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Options;
using NexusReader.Application.Abstractions.Persistence;
using NexusReader.Application.Common;
using NexusReader.Application.DTOs.AI;

namespace NexusReader.Application.Queries.Intelligence;

/// <summary>
/// MediatR query to request global intelligence hybrid Q&A context.
/// </summary>
public record GetGlobalIntelligenceQuery(string QueryText, string UserId, string TenantId = "global") 
    : IRequest<Result<IntelligenceResponse>>;

/// <summary>
/// Request schema for global hybrid search queries.
/// </summary>
public record GetGlobalIntelligenceRequest(string QueryText);

/// <summary>
/// Response schema returning generated AI text, paywall status, and locked publishing details.
/// </summary>
public record IntelligenceResponse(
    string ResponseText, 
    bool HasPaywall, 
    Guid? LockedBookId, 
    string? LockedBookTitle,
    List<CitationDto>? Citations = null);

/// <summary>
/// Handles <see cref="GetGlobalIntelligenceQuery"/> by performing local/global dual searches,
/// executing monetization rules, and invoking Chat AI with appropriate gating logic.
/// </summary>
public class GetGlobalIntelligenceQueryHandler : IRequestHandler<GetGlobalIntelligenceQuery, Result<IntelligenceResponse>>
{
    private readonly IUserLibraryStore _userLibraryStore;
    private readonly IVectorSearchStore _vectorSearchStore;
    private readonly IChatClient _chatClient;
    private readonly RagMonetizationOptions _options;

    public GetGlobalIntelligenceQueryHandler(
        IUserLibraryStore userLibraryStore,
        IVectorSearchStore vectorSearchStore,
        IChatClient chatClient,
        IOptions<RagMonetizationOptions> options)
    {
        _userLibraryStore = userLibraryStore;
        _vectorSearchStore = vectorSearchStore;
        _chatClient = chatClient;
        _options = options.Value;
    }

    public async Task<Result<IntelligenceResponse>> Handle(GetGlobalIntelligenceQuery request, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(request.QueryText))
        {
            return Result.Fail("Question cannot be empty.");
        }

        try
        {
            // Step A: Fetch whitelisted BookIds
            var whitelistedBookIds = await _userLibraryStore.GetOwnedBookIdsAsync(request.UserId, cancellationToken);

            // Step B & C: Vector Dual-Search with Resilient Trapping
            List<VectorChunk> globalChunks = new();
            List<VectorChunk> localChunks = new();
            double globalScore = 0.0;
            double localScore = 0.0;

            try
            {
                // Execute searches
                globalChunks = await _vectorSearchStore.SearchGlobalAsync(request.QueryText, request.TenantId, limit: 3, cancellationToken);
                globalScore = globalChunks.Any() ? Math.Max(0.0, globalChunks.Max(c => c.Score)) : 0.0;

                if (whitelistedBookIds.Any())
                {
                    localChunks = await _vectorSearchStore.SearchLocalAsync(request.QueryText, request.TenantId, whitelistedBookIds, limit: 3, cancellationToken);
                    localScore = localChunks.Any() ? Math.Max(0.0, localChunks.Max(c => c.Score)) : 0.0;
                }
            }
            catch (Exception ex)
            {
                // Resilient Error Trapping: transform connectivity anomalies into domain-friendly errors
                return Result.Fail(new Error("Serwer wyszukiwania semantycznego jest tymczasowo niedostępny. Spróbuj ponownie później.").CausedBy(ex));
            }

            // Step D: Evaluate Monetization Thresholds
            bool triggerPaywall = false;

            if (localScore == 0.0 && globalScore > _options.BaselineThreshold)
            {
                triggerPaywall = true;
            }
            else if ((globalScore - localScore) > _options.DeltaThreshold && globalScore > _options.UpgradeThreshold)
            {
                triggerPaywall = true;
            }

            var chosenChunks = triggerPaywall ? globalChunks : localChunks;

            // Fetch book titles for citations/paywall metadata
            var chunkEbookIds = chosenChunks
                .Where(c => Guid.TryParse(c.EbookId, out _))
                .Select(c => Guid.Parse(c.EbookId))
                .Distinct()
                .ToList();

            var bookTitles = await _userLibraryStore.GetBookTitlesAsync(chunkEbookIds, cancellationToken);

            // Step E: Identify locked book if paywall triggered
            Guid? lockedBookId = null;
            string? lockedBookTitle = null;

            if (triggerPaywall && globalChunks.Any())
            {
                var topGlobalChunk = globalChunks.OrderByDescending(c => c.Score).First();
                if (Guid.TryParse(topGlobalChunk.EbookId, out var parsedLockedId))
                {
                    lockedBookId = parsedLockedId;
                    bookTitles.TryGetValue(parsedLockedId, out lockedBookTitle);
                    if (string.IsNullOrEmpty(lockedBookTitle))
                    {
                        lockedBookTitle = "Nieznana książka";
                    }
                }
            }

            // Format context blocks for LLM
            var relatedContexts = new List<string>();
            foreach (var chunk in chosenChunks)
            {
                var sourceId = chunk.EbookId;
                relatedContexts.Add($"[Source ID: {sourceId}] {chunk.Content}");
            }
            var contextBlocksText = string.Join("\n\n", relatedContexts);

            // Build LLM prompts
            var systemPrompt = "You are an advanced, extremely precise Fact-Checking AI assistant. Your task is to answer the user's question using ONLY the provided context blocks.\n" +
                               "Rely EXCLUSIVELY on the provided context. Do NOT use any pre-existing external knowledge, facts, or assumptions.\n" +
                               "If the context does not contain the answer, say: 'I cannot answer this based on the provided book context.'";

            if (triggerPaywall)
            {
                var localScorePercent = (int)Math.Round(localScore * 100);
                var globalScorePercent = (int)Math.Round(globalScore * 100);
                var resolvedTitle = lockedBookTitle ?? "Nieznana książka";

                systemPrompt += $"\n\nCRITICAL: You are operating in TEASER mode. The user does not own the source document named '{resolvedTitle}'. You are strictly allowed to provide only a 1-sentence foundational definition or answer based on the context to prove the system knows the solution. DO NOT output code blocks, implementation details, or bullet points. You must immediately terminate your response with this exact token format: [PAYWALL_TRIGGER:{lockedBookId}:{resolvedTitle}:{localScorePercent}:{globalScorePercent}].";
            }

            var messages = new List<Microsoft.Extensions.AI.ChatMessage>
            {
                new(Microsoft.Extensions.AI.ChatRole.System, systemPrompt),
                new(Microsoft.Extensions.AI.ChatRole.User, $"Context:\n{contextBlocksText}\n\nQuestion: {request.QueryText}")
            };

            var chatOptions = new ChatOptions
            {
                Temperature = 0.0f,
                MaxOutputTokens = 1000
            };

            var chatResponse = await _chatClient.GetResponseAsync(messages, chatOptions, cancellationToken);
            var responseText = chatResponse.Text?.Trim() ?? string.Empty;

            // Ensure the paywall token is appended if LLM misses it in teaser mode
            if (triggerPaywall)
            {
                var localScorePercent = (int)Math.Round(localScore * 100);
                var globalScorePercent = (int)Math.Round(globalScore * 100);
                var resolvedTitle = lockedBookTitle ?? "Nieznana książka";
                var paywallToken = $"[PAYWALL_TRIGGER:{lockedBookId}:{resolvedTitle}:{localScorePercent}:{globalScorePercent}]";

                if (!responseText.Contains("[PAYWALL_TRIGGER:"))
                {
                    responseText = responseText.Trim() + " " + paywallToken;
                }
            }

            // Build citations list
            var citations = new List<CitationDto>();
            foreach (var chunk in chosenChunks)
            {
                var sourceBookName = "Unknown";
                if (Guid.TryParse(chunk.EbookId, out var parsedId) && bookTitles.TryGetValue(parsedId, out var title))
                {
                    sourceBookName = title;
                }

                citations.Add(new CitationDto
                {
                    CitationId = chunk.EbookId,
                    Snippet = chunk.Content,
                    SourceBook = sourceBookName,
                    Author = null,
                    PageNumber = null
                });
            }

            return Result.Ok(new IntelligenceResponse(
                ResponseText: responseText,
                HasPaywall: triggerPaywall,
                LockedBookId: lockedBookId,
                LockedBookTitle: lockedBookTitle,
                Citations: citations
            ));
        }
        catch (Exception ex)
        {
            return Result.Fail(new Error("Nieoczekiwany błąd serwera podczas przetwarzania zapytania.").CausedBy(ex));
        }
    }
}
