using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Meezi.API.Models.Public;
using Meezi.Infrastructure.Data;

namespace Meezi.API.Services;

public interface ICoffeeAdvisorService
{
    Task<(CoffeeAdvisorResultDto? Data, string? ErrorCode, string? Message)> RecommendAsync(
        CoffeeAdvisorRequest request,
        CancellationToken cancellationToken = default);
}

public class CoffeeAdvisorService : ICoffeeAdvisorService
{
    private static readonly JsonSerializerOptions JsonOpts = new() { PropertyNameCaseInsensitive = true };

    private readonly AppDbContext _db;
    private readonly IOpenAiChatService _openAi;
    private readonly ILogger<CoffeeAdvisorService> _logger;

    public CoffeeAdvisorService(
        AppDbContext db,
        IOpenAiChatService openAi,
        ILogger<CoffeeAdvisorService> logger)
    {
        _db = db;
        _openAi = openAi;
        _logger = logger;
    }

    public async Task<(CoffeeAdvisorResultDto? Data, string? ErrorCode, string? Message)> RecommendAsync(
        CoffeeAdvisorRequest request,
        CancellationToken cancellationToken = default)
    {
        var purpose = request.Purpose?.Trim();
        if (string.IsNullOrWhiteSpace(purpose) || purpose.Length < 3)
            return (null, "INVALID_REQUEST", "Describe what you need (at least 3 characters).");

        if (!await _openAi.IsConfiguredForCoffeeAdvisorAsync(cancellationToken))
            return (null, "AI_NOT_CONFIGURED", "Coffee advisor is not available right now.");

        var menuLines = await LoadMenuContextAsync(request.CafeSlug, cancellationToken);
        var systemPrompt =
            """
            You are a specialty coffee advisor for Iranian cafés. Respond ONLY with valid JSON (no markdown).
            Schema: { "summary": string (1-2 sentences in Persian), "picks": [ { "name": string, "reason": string (Persian), "menuItemId": string|null } ] }
            Rules: suggest 1-3 drinks; prefer items from the menu list when provided; match the guest's purpose (energy, relax, meeting, dessert pairing, etc.); be concise and friendly in Persian.
            """;
        var userPrompt = $"""
            Guest purpose: {purpose}
            {(menuLines.Count > 0 ? "Café menu (id | name | description):\n" + string.Join("\n", menuLines) : "No specific café menu — suggest classic café drinks.")}
            """;

        string? json;
        try
        {
            json = await _openAi.CompleteJsonAsync(systemPrompt, userPrompt, cancellationToken);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Coffee advisor OpenAI call failed");
            return (null, "AI_FAILED", "Could not get a recommendation. Try again later.");
        }

        if (string.IsNullOrWhiteSpace(json))
            return (null, "AI_FAILED", "Could not get a recommendation. Try again later.");

        try
        {
            var parsed = JsonSerializer.Deserialize<AdvisorJson>(json, JsonOpts);
            if (parsed is null || string.IsNullOrWhiteSpace(parsed.Summary))
                return (null, "AI_FAILED", "Invalid advisor response.");

            var picks = (parsed.Picks ?? [])
                .Where(p => !string.IsNullOrWhiteSpace(p.Name))
                .Take(3)
                .Select(p => new CoffeeAdvisorPickDto(
                    p.Name!.Trim(),
                    p.Reason?.Trim() ?? "",
                    string.IsNullOrWhiteSpace(p.MenuItemId) ? null : p.MenuItemId.Trim()))
                .ToList();

            return (new CoffeeAdvisorResultDto(parsed.Summary.Trim(), picks), null, null);
        }
        catch (JsonException ex)
        {
            _logger.LogWarning(ex, "Coffee advisor JSON parse failed");
            return (null, "AI_FAILED", "Could not parse advisor response.");
        }
    }

    private async Task<IReadOnlyList<string>> LoadMenuContextAsync(string? slug, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(slug))
            return [];

        var cafeId = await _db.Cafes.AsNoTracking()
            .Where(c => c.Slug == slug.Trim() && c.DeletedAt == null)
            .Select(c => c.Id)
            .FirstOrDefaultAsync(cancellationToken);
        if (cafeId is null)
            return [];

        var items = await _db.MenuItems.AsNoTracking()
            .Where(i => i.CafeId == cafeId && i.IsAvailable && i.DeletedAt == null)
            .OrderBy(i => i.Name)
            .Take(40)
            .Select(i => new { i.Id, i.Name, i.Description })
            .ToListAsync(cancellationToken);

        return items
            .Select(i => $"{i.Id} | {i.Name} | {(string.IsNullOrWhiteSpace(i.Description) ? "-" : i.Description)}")
            .ToList();
    }

    private sealed class AdvisorJson
    {
        public string? Summary { get; set; }
        public List<AdvisorPickJson>? Picks { get; set; }
    }

    private sealed class AdvisorPickJson
    {
        public string? Name { get; set; }
        public string? Reason { get; set; }
        public string? MenuItemId { get; set; }
    }
}
