using System.Net.Http.Json;
using System.Text.Json;
using ROLAC.API.Data;

namespace ROLAC.API.Services.Ai;

/// <summary>
/// Translates and classifies an expense via the Anthropic Claude Messages API. It forces a single
/// tool call (<c>tool_choice</c> → <c>classify_expense</c>) whose <c>input_schema</c> matches our
/// answer shape, so the model returns structured JSON in a <c>tool_use</c> block. The catalog,
/// prompt, and id validation come from <see cref="ExpenseAiServiceBase"/>; this class only owns the
/// Claude HTTP call + parse. Forced tool use works on every Claude model, so the configured
/// model can be swapped (e.g. to a cheaper model) without code changes.
/// </summary>
public sealed class ClaudeExpenseAiService : ExpenseAiServiceBase
{
    private const string BaseUrl          = "https://api.anthropic.com/v1";
    private const string AnthropicVersion = "2023-06-01";

    private readonly HttpClient _http;
    private readonly IChurchAiConfigProvider _config;
    private readonly ILogger<ClaudeExpenseAiService> _logger;

    public ClaudeExpenseAiService(
        HttpClient http,
        IChurchAiConfigProvider config,
        AppDbContext db,
        ILogger<ClaudeExpenseAiService> logger)
        : base(db)
    {
        _http = http;
        _config = config;
        _logger = logger;
    }

    protected override async Task<ModelAnswer?> CallModelAsync(string prompt, CancellationToken ct)
    {
        var cfg = await _config.GetAsync(ct);
        if (string.IsNullOrWhiteSpace(cfg.ClaudeApiKey))
        {
            _logger.LogWarning("Claude API key is not configured; expense AI assist is disabled.");
            return null;
        }

        try
        {
            var payload = new
            {
                model = cfg.ClaudeModel,
                max_tokens = 1024,
                tools = new[]
                {
                    new
                    {
                        name = "classify_expense",
                        description = "Record the English translation and the chosen expense category ids for the expense.",
                        input_schema = new
                        {
                            type = "object",
                            properties = new
                            {
                                chineseDescription = new { type = "string" },
                                englishDescription = new { type = "string" },
                                groupId = new { type = "integer" },
                                subCategoryId = new { type = "integer" },
                                confidence = new { type = "number" },
                            },
                            required = new[] { "chineseDescription", "englishDescription", "groupId", "subCategoryId", "confidence" },
                        },
                    },
                },
                tool_choice = new { type = "tool", name = "classify_expense" },
                messages = new[]
                {
                    new { role = "user", content = prompt },
                },
            };

            var url = $"{BaseUrl}/messages";
            using var request = new HttpRequestMessage(HttpMethod.Post, url)
            {
                Content = JsonContent.Create(payload),
            };
            request.Headers.Add("x-api-key", cfg.ClaudeApiKey);
            request.Headers.Add("anthropic-version", AnthropicVersion);

            using var response = await _http.SendAsync(request, ct);
            if (!response.IsSuccessStatusCode)
            {
                var body = await response.Content.ReadAsStringAsync(ct);
                _logger.LogWarning("Claude returned {Status}: {Body}", (int)response.StatusCode, body);
                return null;
            }

            // The forced tool call lands in content[] as a tool_use block; its `input` is our object.
            using var doc = JsonDocument.Parse(await response.Content.ReadAsStreamAsync(ct));
            foreach (var block in doc.RootElement.GetProperty("content").EnumerateArray())
            {
                if (block.GetProperty("type").GetString() != "tool_use") continue;

                var parsed = block.GetProperty("input").Deserialize<ClaudeAnswer>(
                    new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
                if (parsed is null) return null;

                return new ModelAnswer(parsed.EnglishDescription, parsed.ChineseDescription, parsed.GroupId, parsed.SubCategoryId, parsed.Confidence);
            }

            _logger.LogWarning("Claude response contained no tool_use block.");
            return null;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Claude expense AI assist failed.");
            return null;
        }
    }

    /// <summary>Shape of the classify_expense tool input the model fills in.</summary>
    private sealed class ClaudeAnswer
    {
        public string? EnglishDescription { get; set; }
        public string? ChineseDescription { get; set; }
        public int     GroupId            { get; set; }
        public int     SubCategoryId      { get; set; }
        public double  Confidence         { get; set; }
    }
}
