using AxCopilot.Models;

namespace AxCopilot.Services;

/// <summary>
/// 자동 모델 라우팅 결과.
/// </summary>
public record ModelRouteResult(
    string Service,
    string Model,
    string DisplayName,
    string DetectedIntent,
    double Confidence);

/// <summary>
/// 사용자 메시지의 인텐트를 분석하여 최적 모델을 선택하는 라우터.
/// 현재는 잠금 상태(EnableAutoRouter=false)이며, 사내 서버 확정 후 활성화 예정.
///
/// 개발자 가이드:
/// - 하드코딩 기본값 수정: GetDefaultCapabilities() 메서드
/// - 사내 Ollama/vLLM 모델 추가: GetDefaultCapabilities()에 항목 추가
/// - 확신도 조정: Route() 메서드의 confidence 체크 로직
/// </summary>
public class ModelRouterService
{
    private readonly SettingsService _settings;

    public ModelRouterService(SettingsService settings)
    {
        _settings = settings;
    }

    /// <summary>
    /// 사용자 메시지를 분석하여 최적 모델을 선택합니다.
    /// </summary>
    /// <returns>라우팅 결과. null이면 기본 모델 유지.</returns>
    public ModelRouteResult? Route(string userMessage)
    {
        var llm = _settings.Settings.Llm;

        // 1. 자동 라우팅 비활성이면 즉시 반환
        if (!llm.EnableAutoRouter)
            return null;

        // 2. 인텐트 감지
        var (category, confidence) = IntentDetector.Detect(userMessage);

        // 3. 확신도 미달이면 기본 모델 유지
        if (confidence < llm.AutoRouterConfidence)
            return null;

        // 4. 일반 대화는 라우팅 불필요
        if (category == IntentDetector.Categories.General)
            return null;

        // 5. 후보 수집: 사용자 설정 + 하드코딩 기본값
        var candidates = GatherCandidates(llm);
        if (candidates.Count == 0)
            return null;

        // 6. 현재 모델 식별
        var currentService = llm.Service.ToLowerInvariant();
        var currentModel = ResolveCurrentModel(llm);

        // 7. 각 후보의 해당 카테고리 점수로 정렬
        var best = candidates
            .Where(c => c.Scores.ContainsKey(category))
            .OrderByDescending(c => c.Scores[category])
            .FirstOrDefault();

        if (best == null)
            return null;

        // 8. 현재 모델이 이미 최적이면 전환 불필요
        var currentScore = candidates
            .FirstOrDefault(c => c.Service.Equals(currentService, StringComparison.OrdinalIgnoreCase)
                              && c.Model.Equals(currentModel, StringComparison.OrdinalIgnoreCase));
        if (currentScore != null
            && currentScore.Scores.TryGetValue(category, out var curVal)
            && best.Scores[category] - curVal < 0.1) // 차이가 0.1 미만이면 전환 안함
        {
            return null;
        }

        return new ModelRouteResult(
            Service: best.Service,
            Model: best.Model,
            DisplayName: best.Alias,
            DetectedIntent: category,
            Confidence: confidence);
    }

    /// <summary>
    /// 사용 가능한 모델 후보를 수집합니다.
    /// 사용자 설정(ModelCapabilities) 우선, 없으면 하드코딩 기본값 사용.
    /// </summary>
    private List<ModelCapability> GatherCandidates(LlmSettings llm)
    {
        var candidates = new List<ModelCapability>();

        // 사용자 설정 후보
        foreach (var cap in llm.ModelCapabilities)
        {
            if (!cap.Enabled) continue;
            if (!IsServiceAvailable(llm, cap.Service)) continue;
            candidates.Add(cap);
        }

        // 하드코딩 기본값 (사용자 설정에 없는 서비스/모델만 추가)
        var defaults = GetDefaultCapabilities();
        foreach (var def in defaults)
        {
            if (!IsServiceAvailable(llm, def.Service)) continue;
            // 이미 사용자 설정에 있으면 스킵
            if (candidates.Any(c => c.Service.Equals(def.Service, StringComparison.OrdinalIgnoreCase)
                                 && c.Model.Equals(def.Model, StringComparison.OrdinalIgnoreCase)))
                continue;
            candidates.Add(def);
        }

        return candidates;
    }

    /// <summary>
    /// 해당 서비스가 사용 가능한지 확인 (API 키 또는 엔드포인트 설정 여부).
    /// </summary>
    private static bool IsServiceAvailable(LlmSettings llm, string service)
    {
        return service.ToLowerInvariant() switch
        {
            "gemini" => !string.IsNullOrEmpty(llm.GeminiApiKey),
            "claude" => !string.IsNullOrEmpty(llm.ClaudeApiKey),
            "ollama" => !string.IsNullOrEmpty(llm.OllamaEndpoint),
            "vllm"   => !string.IsNullOrEmpty(llm.VllmEndpoint),
            _        => false,
        };
    }

    /// <summary>현재 활성 모델명을 반환 (암호화 고려).</summary>
    private static string ResolveCurrentModel(LlmSettings llm)
    {
        if (llm.Service is "ollama" or "vllm" && !string.IsNullOrEmpty(llm.Model))
            return CryptoService.DecryptIfEnabled(llm.Model, llm.EncryptionEnabled);
        return llm.Service.ToLowerInvariant() switch
        {
            "gemini" => llm.GeminiModel,
            "claude" => llm.ClaudeModel,
            _ => llm.Model,
        };
    }

    /// <summary>
    /// 하드코딩된 기본 모델 능력 점수.
    ///
    /// ★ 개발자 수정 포인트 ★
    /// 사내 Ollama/vLLM 서버가 확정되면 여기에 항목을 추가하세요.
    /// 예시:
    ///   new ModelCapability
    ///   {
    ///       Service = "ollama", Model = "codellama:34b", Alias = "CodeLlama 34B",
    ///       Scores = new() { ["coding"] = 0.9, ["analysis"] = 0.6, ... }
    ///   }
    ///
    /// 카테고리 키: "coding", "translation", "analysis", "creative", "document", "math"
    /// 점수 범위: 0.0 (부적합) ~ 1.0 (최적)
    /// </summary>
    public static List<ModelCapability> GetDefaultCapabilities()
    {
        return new List<ModelCapability>
        {
            // ── Gemini ──
            new()
            {
                Service = "gemini", Model = "gemini-2.5-flash", Alias = "Gemini 2.5 Flash",
                Scores = new()
                {
                    ["coding"] = 0.70, ["translation"] = 0.70, ["analysis"] = 0.85,
                    ["creative"] = 0.65, ["document"] = 0.70, ["math"] = 0.90,
                }
            },
            new()
            {
                Service = "gemini", Model = "gemini-2.5-pro", Alias = "Gemini 2.5 Pro",
                Scores = new()
                {
                    ["coding"] = 0.85, ["translation"] = 0.80, ["analysis"] = 0.90,
                    ["creative"] = 0.75, ["document"] = 0.80, ["math"] = 0.95,
                }
            },

            // ── Claude ──
            new()
            {
                Service = "claude", Model = "claude-sonnet-4-6", Alias = "Claude Sonnet 4.6",
                Scores = new()
                {
                    ["coding"] = 0.90, ["translation"] = 0.85, ["analysis"] = 0.90,
                    ["creative"] = 0.90, ["document"] = 0.85, ["math"] = 0.80,
                }
            },
            new()
            {
                Service = "claude", Model = "claude-opus-4-6", Alias = "Claude Opus 4.6",
                Scores = new()
                {
                    ["coding"] = 0.95, ["translation"] = 0.90, ["analysis"] = 0.95,
                    ["creative"] = 0.95, ["document"] = 0.90, ["math"] = 0.85,
                }
            },
            new()
            {
                Service = "claude", Model = "claude-haiku-4-5", Alias = "Claude Haiku 4.5",
                Scores = new()
                {
                    ["coding"] = 0.65, ["translation"] = 0.70, ["analysis"] = 0.65,
                    ["creative"] = 0.60, ["document"] = 0.60, ["math"] = 0.60,
                }
            },

            // ── 사내 Ollama/vLLM (예시 — 서버 확정 후 수정) ──
            // new()
            // {
            //     Service = "ollama", Model = "codellama:34b", Alias = "CodeLlama 34B",
            //     Scores = new()
            //     {
            //         ["coding"] = 0.85, ["translation"] = 0.40, ["analysis"] = 0.55,
            //         ["creative"] = 0.35, ["document"] = 0.40, ["math"] = 0.50,
            //     }
            // },
            // new()
            // {
            //     Service = "vllm", Model = "qwen2.5-72b", Alias = "Qwen2.5 72B",
            //     Scores = new()
            //     {
            //         ["coding"] = 0.80, ["translation"] = 0.85, ["analysis"] = 0.80,
            //         ["creative"] = 0.75, ["document"] = 0.75, ["math"] = 0.80,
            //     }
            // },
        };
    }
}
