using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Themes;
using System.Net.Http;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Windows;

namespace AxCopilot.Handlers;

/// <summary>
/// 수식 계산기 핸들러. "=" 프리픽스로 사용합니다.
/// 예: = 1+2*3          →  7
///     = sqrt(16)       →  4
///     = 100km in miles →  단위 변환
///     = 100 USD to KRW →  통화 변환 (실시간 환율)
/// </summary>
public class CalculatorHandler : IActionHandler
{
    public string? Prefix => "=";

    public PluginMetadata Metadata => new(
        "Calculator",
        "수식 계산기 — = 뒤에 수식 입력",
        "1.0",
        "AX");

    public async Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
    {
        if (string.IsNullOrWhiteSpace(query))
        {
            return
            [
                new LauncherItem(
                    "수식을 입력하세요",
                    "예: 1+2*3 · sqrt(16) · 100km in miles · 100 USD to KRW",
                    null, null, Symbol: Symbols.Calculator)
            ];
        }

        var trimmed = query.Trim();

        // ─── today+Nd / today-Nd 날짜 단축 계산 (= 접두어에서 date 핸들러 위임) ─
        if (DateShortcut.TryMatch(trimmed, out var dateItems))
            return dateItems!;

        // ─── 통화 변환 우선 감지 ──────────────────────────────────────────────
        if (CurrencyConverter.IsCurrencyQuery(trimmed))
        {
            return await CurrencyConverter.ConvertAsync(trimmed, ct);
        }

        // ─── 단위 변환 (명시적 목표: 100km in miles) ─────────────────────────
        if (UnitConverter.TryConvert(trimmed, out var convertResult))
        {
            return
            [
                new LauncherItem(
                    convertResult!,
                    $"{trimmed}  ·  Enter로 클립보드에 복사",
                    null, convertResult, Symbol: Symbols.Calculator),
            ];
        }

        // ─── 단위 단축 자동 제안 (20km, 100f, 5lb …) ──────────────────────────
        var autoSuggest = UnitConverter.AutoSuggest(trimmed);
        if (autoSuggest.Count > 0)
            return autoSuggest;

        // ─── 수식 계산 ────────────────────────────────────────────────────────
        try
        {
            var value  = MathEvaluator.Evaluate(trimmed);
            var result = FormatResult(value);

            return
            [
                new LauncherItem(
                    result,
                    $"{trimmed} = {result}  ·  Enter로 클립보드에 복사",
                    null, result, Symbol: Symbols.Calculator),
            ];
        }
        catch (Exception ex)
        {
            return
            [
                new LauncherItem(
                    "계산할 수 없습니다",
                    ex.Message,
                    null, null, Symbol: Symbols.Error)
            ];
        }
    }

    public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
    {
        if (item.Data is string result)
        {
            try { Clipboard.SetText(result); }
            catch (Exception) { /* 클립보드 접근 실패 무시 */ }
        }
        return Task.CompletedTask;
    }

    // ─── 결과 포맷 ─────────────────────────────────────────────────────────

    private static string FormatResult(double value)
    {
        if (double.IsNaN(value))      return "NaN";
        if (double.IsPositiveInfinity(value)) return "∞";
        if (double.IsNegativeInfinity(value)) return "-∞";

        // 정수이고 너무 크지 않으면 천 단위 구분 없이 정수로 표시
        if (value == Math.Floor(value) && Math.Abs(value) < 1e15)
            return ((long)value).ToString();

        // 소수점 10자리까지, 불필요한 0은 제거
        return value.ToString("G10", System.Globalization.CultureInfo.InvariantCulture);
    }
}

// ─── 통화 변환 ──────────────────────────────────────────────────────────────────

/// <summary>
/// "100 USD to KRW", "50 EUR in JPY" 형식의 통화 변환.
/// open.er-api.com 무료 API 사용 (1시간 캐시).
/// </summary>
internal static class CurrencyConverter
{
    // 패턴: <숫자> <통화코드 3자리> in|to <통화코드 3자리>
    private static readonly Regex _pattern = new(
        @"^(\d+(?:\.\d+)?)\s+([A-Za-z]{3})\s+(?:to|in)\s+([A-Za-z]{3})$",
        RegexOptions.IgnoreCase | RegexOptions.Compiled);

    // 캐시: base currency → (fetched at, rates dict)
    private static readonly Dictionary<string, (DateTime At, Dictionary<string, double> Rates)> _cache = new();
    private static readonly HttpClient _http = new() { Timeout = TimeSpan.FromSeconds(5) };
    private static readonly TimeSpan CacheTtl = TimeSpan.FromHours(1);

    // 주요 통화 이름
    private static readonly Dictionary<string, string> _names = new(StringComparer.OrdinalIgnoreCase)
    {
        ["KRW"] = "원",      ["USD"] = "달러",    ["EUR"] = "유로",
        ["JPY"] = "엔",      ["GBP"] = "파운드",  ["CNY"] = "위안",
        ["HKD"] = "홍콩달러", ["SGD"] = "싱가포르달러", ["CAD"] = "캐나다달러",
        ["AUD"] = "호주달러", ["CHF"] = "스위스프랑",   ["TWD"] = "대만달러",
        ["MXN"] = "멕시코페소", ["BRL"] = "브라질헤알",  ["INR"] = "인도루피",
        ["RUB"] = "루블",     ["THB"] = "바트",    ["VND"] = "동",
        ["IDR"] = "루피아",   ["MYR"] = "링깃",    ["PHP"] = "페소",
        ["NZD"] = "뉴질랜드달러", ["SEK"] = "크로나", ["NOK"] = "크로나(노르)",
        ["DKK"] = "크로나(덴)",  ["AED"] = "디르함", ["SAR"] = "리얄",
    };

    public static bool IsCurrencyQuery(string input)
        => _pattern.IsMatch(input.Trim());

    public static async Task<IEnumerable<LauncherItem>> ConvertAsync(string input, CancellationToken ct)
    {
        var m = _pattern.Match(input.Trim());
        if (!m.Success)
            return [new LauncherItem("통화 형식 오류", "예: 100 USD to KRW", null, null, Symbol: Symbols.Error)];

        if (!double.TryParse(m.Groups[1].Value,
                System.Globalization.NumberStyles.Float,
                System.Globalization.CultureInfo.InvariantCulture,
                out double amount))
            return [new LauncherItem("숫자 형식 오류", "", null, null, Symbol: Symbols.Error)];

        var from = m.Groups[2].Value.ToUpperInvariant();
        var to   = m.Groups[3].Value.ToUpperInvariant();

        try
        {
            var rates = await GetRatesAsync(from, ct);
            if (rates == null)
                return [new LauncherItem("환율 조회 실패", "네트워크 연결을 확인하세요", null, null, Symbol: Symbols.Warning)];

            if (!rates.TryGetValue(to, out double rate))
                return [new LauncherItem($"지원하지 않는 통화: {to}", "3자리 ISO 4217 코드를 입력하세요", null, null, Symbol: Symbols.Error)];

            double result = amount * rate;
            var fromName = _names.TryGetValue(from, out var fn) ? fn : from;
            var toName   = _names.TryGetValue(to,   out var tn) ? tn : to;

            // 금액 포맷
            string resultStr = to == "KRW" || to == "JPY" || to == "IDR" || to == "VND"
                ? result.ToString("N0", System.Globalization.CultureInfo.CurrentCulture)
                : result.ToString("N2", System.Globalization.CultureInfo.CurrentCulture);

            string rateStr = rate < 0.01
                ? rate.ToString("G4", System.Globalization.CultureInfo.InvariantCulture)
                : rate.ToString("N4", System.Globalization.CultureInfo.CurrentCulture);

            var display = $"{resultStr} {to}";

            return
            [
                new LauncherItem(
                    $"{amount:N2} {from}  =  {display}",
                    $"1 {from}({fromName}) = {rateStr} {to}({toName})  ·  Enter로 복사",
                    null, display, Symbol: Symbols.Calculator),
                new LauncherItem(
                    $"숫자만: {resultStr}",
                    "Enter로 숫자만 복사",
                    null, resultStr, Symbol: Symbols.Calculator),
            ];
        }
        catch (OperationCanceledException)
        {
            return [];
        }
        catch (Exception ex)
        {
            LogService.Warn($"환율 조회 오류: {ex.Message}");
            return [new LauncherItem("환율 조회 실패", ex.Message, null, null, Symbol: Symbols.Warning)];
        }
    }

    private static async Task<Dictionary<string, double>?> GetRatesAsync(string baseCurrency, CancellationToken ct)
    {
        if (_cache.TryGetValue(baseCurrency, out var cached) &&
            (DateTime.Now - cached.At) < CacheTtl)
            return cached.Rates;

        var url = $"https://open.er-api.com/v6/latest/{baseCurrency}";
        var json = await _http.GetStringAsync(url, ct);

        using var doc = JsonDocument.Parse(json);
        var root = doc.RootElement;

        if (!root.TryGetProperty("result", out var resultEl) ||
            resultEl.GetString() != "success")
            return null;

        if (!root.TryGetProperty("rates", out var ratesEl))
            return null;

        var rates = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase);
        foreach (var prop in ratesEl.EnumerateObject())
            rates[prop.Name] = prop.Value.GetDouble();

        _cache[baseCurrency] = (DateTime.Now, rates);
        return rates;
    }
}
