using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using FluentResults;
using MediatR;
using Microsoft.Extensions.Logging;
using NexusReader.Application.Abstractions.Persistence;
using NexusReader.Application.Queries.Recommendations;

namespace NexusReader.Infrastructure.Queries;

/// <summary>
/// Handles <see cref="GetContextualRecommendationsQuery"/> by discovering the active reading state,
/// performing semantic search using <see cref="IVectorSearchStore"/> with book exclusion, and mapping upsells.
/// </summary>
public class GetContextualRecommendationsQueryHandler : IRequestHandler<GetContextualRecommendationsQuery, Result<ContextualRecommendationResponse>>
{
    private readonly IUserReadingStateStore _readingStateStore;
    private readonly IUserLibraryStore _libraryStore;
    private readonly IVectorSearchStore _vectorSearchStore;
    private readonly ILogger<GetContextualRecommendationsQueryHandler> _logger;

    /// <summary>
    /// Initializes a new instance of <see cref="GetContextualRecommendationsQueryHandler"/>.
    /// </summary>
    public GetContextualRecommendationsQueryHandler(
        IUserReadingStateStore readingStateStore,
        IUserLibraryStore libraryStore,
        IVectorSearchStore vectorSearchStore,
        ILogger<GetContextualRecommendationsQueryHandler> logger)
    {
        _readingStateStore = readingStateStore;
        _libraryStore = libraryStore;
        _vectorSearchStore = vectorSearchStore;
        _logger = logger;
    }

    /// <inheritdoc />
    public async Task<Result<ContextualRecommendationResponse>> Handle(GetContextualRecommendationsQuery request, CancellationToken cancellationToken)
    {
        if (string.IsNullOrEmpty(request.UserId))
        {
            return Result.Fail("UserId cannot be empty.");
        }

        try
        {
            // Step 1: Discover active reading state
            var (ebookId, chapterId, tenantId) = await _readingStateStore.GetActiveReadingStateAsync(request.UserId, cancellationToken);
            if (ebookId == null)
            {
                _logger.LogInformation("[Recommendations] No active reading state for user {UserId}. Returning empty list.", request.UserId);
                return Result.Ok(new ContextualRecommendationResponse(new List<RecommendationDto>()));
            }

            // Step 2: Fetch specific content associated with active ChapterId
            string? chapterContent = null;
            if (!string.IsNullOrEmpty(chapterId))
            {
                chapterContent = await _readingStateStore.GetChapterContentAsync(chapterId, cancellationToken);
            }

            // Guard: empty chapter content cannot produce a meaningful embedding
            if (string.IsNullOrWhiteSpace(chapterContent))
            {
                _logger.LogWarning("[Recommendations] Chapter content is empty for chapterId={ChapterId}. Returning empty list.", chapterId);
                return Result.Ok(new ContextualRecommendationResponse(new List<RecommendationDto>()));
            }

            // Step 3: Perform similarity search using IVectorSearchStore
            var resolvedTenantId = tenantId ?? "global";
            _logger.LogDebug("[Recommendations] Performing vector search for user {UserId}, book {EbookId}, tenant {TenantId}.", request.UserId, ebookId, resolvedTenantId);

            var searchResults = await _vectorSearchStore.SearchGlobalExcludeAsync(
                chapterContent,
                resolvedTenantId,
                ebookId.Value,
                limit: 2,
                cancellationToken: cancellationToken
            );

            // Step 4: Process recommendations and cross-reference owned books
            var ownedBookIds = await _libraryStore.GetOwnedBookIdsAsync(request.UserId, cancellationToken);
            var recommendations = new List<RecommendationDto>();

            foreach (var point in searchResults)
            {
                var targetEbookIdStr = point.EbookId;
                if (!Guid.TryParse(targetEbookIdStr, out var targetEbookId))
                    continue;

                // Load bookTitle from point
                var bookTitle = point.BookTitle;
                if (string.IsNullOrEmpty(bookTitle))
                {
                    bookTitle = "Nieznana książka";
                }

                // Load chapterTitle from point or metadataJson
                var chapterTitle = point.ChapterTitle;
                if (string.IsNullOrEmpty(chapterTitle))
                {
                    chapterTitle = "Wiedza z rozdziału";
                    if (!string.IsNullOrEmpty(point.MetadataJson))
                    {
                        try
                        {
                            using var doc = JsonDocument.Parse(point.MetadataJson);
                            if (doc.RootElement.TryGetProperty("label", out var labelProp))
                            {
                                chapterTitle = labelProp.GetString() ?? chapterTitle;
                            }
                        }
                        catch (JsonException jsonEx)
                        {
                            _logger.LogWarning(jsonEx, "[Recommendations] Failed to parse metadataJson for chunk with ebookId={EbookId}.", targetEbookIdStr);
                        }
                    }
                }

                var isPremiumUpsell = !ownedBookIds.Contains(targetEbookId);
                var matchPercentage = (int)Math.Round(point.Score * 100);

                recommendations.Add(new RecommendationDto(
                    BookTitle: bookTitle,
                    ChapterTitle: chapterTitle,
                    MatchPercentage: matchPercentage,
                    IsPremiumUpsell: isPremiumUpsell,
                    TargetBookId: targetEbookId
                ));
            }

            _logger.LogInformation("[Recommendations] Returning {Count} recommendations for user {UserId}.", recommendations.Count, request.UserId);
            return Result.Ok(new ContextualRecommendationResponse(recommendations));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "[Recommendations] Downstream vector database or state query failed for user {UserId}.", request.UserId);
            return Result.Fail(new Error("Downstream vector database or state query failed.").CausedBy(ex));
        }
    }
}
