using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using AxCopilot.Models;

namespace AxCopilot.Services;

/// <summary>토큰 사용량 정보.</summary>
public record TokenUsage(int PromptTokens, int CompletionTokens)
{
    public int TotalTokens => PromptTokens + CompletionTokens;
}

/// <summary>
/// LLM API 호출 서비스. Ollama / vLLM / Gemini 세 가지 백엔드를 지원합니다.
/// 스트리밍(SSE) 및 비스트리밍 모두 지원합니다.
/// </summary>
public partial class LlmService : IDisposable
{
    private readonly HttpClient _http;
    private readonly SettingsService _settings;
    private string? _systemPrompt;

    private const int MaxRetries = 2;
    private static readonly TimeSpan ChunkTimeout = TimeSpan.FromSeconds(30);

    // ─── 자동 모델 라우팅 오버라이드 ─────────────────────────────────
    private string? _serviceOverride;
    private string? _modelOverride;

    /// <summary>자동 라우팅용 서비스/모델 오버라이드를 설정합니다. 요청 완료 후 반드시 ClearRouteOverride().</summary>
    public void PushRouteOverride(string service, string model)
    {
        _serviceOverride = service;
        _modelOverride = model;
    }

    /// <summary>서비스/모델 오버라이드를 해제합니다.</summary>
    public void ClearRouteOverride()
    {
        _serviceOverride = null;
        _modelOverride = null;
    }

    /// <summary>현재 활성 모델의 서비스명과 모델명을 반환합니다.</summary>
    public (string service, string model) GetCurrentModelInfo() => (ResolveService(), ResolveModel());

    /// <summary>오버라이드를 고려한 실제 서비스명.</summary>
    private string ResolveService() => _serviceOverride ?? _settings.Settings.Llm.Service;

    /// <summary>오버라이드를 고려한 실제 모델명.</summary>
    private string ResolveModel()
    {
        if (_modelOverride != null) return _modelOverride;
        return ResolveModelName();
    }

    /// <summary>지정 서비스의 API 키를 반환합니다.</summary>
    private string ResolveApiKeyForService(string service)
    {
        var llm = _settings.Settings.Llm;
        return service.ToLowerInvariant() switch
        {
            "gemini" => llm.GeminiApiKey,
            "claude" => llm.ClaudeApiKey,
            "vllm"   => CryptoService.DecryptIfEnabled(llm.VllmApiKey, llm.EncryptionEnabled),
            "ollama" => CryptoService.DecryptIfEnabled(llm.OllamaApiKey, llm.EncryptionEnabled),
            _        => "",
        };
    }

    /// <summary>지정 서비스의 엔드포인트를 반환합니다.</summary>
    private string ResolveEndpointForService(string service)
    {
        var llm = _settings.Settings.Llm;
        return service.ToLowerInvariant() switch
        {
            "vllm"   => llm.VllmEndpoint,
            "ollama" => llm.OllamaEndpoint,
            _        => llm.Endpoint,
        };
    }

    /// <summary>가장 최근 요청의 토큰 사용량. 스트리밍/비스트리밍 완료 후 갱신됩니다.</summary>
    public TokenUsage? LastTokenUsage { get; private set; }

    public LlmService(SettingsService settings)
    {
        _settings = settings;
        _http = new HttpClient { Timeout = TimeSpan.FromMinutes(10) };
        LoadSystemPrompt();
    }

    // ─── 시스템 프롬프트 (빌드 경로에서 동적 로딩) ─────────────────────────

    private void LoadSystemPrompt()
    {
        var exeDir = AppContext.BaseDirectory;
        var promptFile = Path.Combine(exeDir, "system_prompt.txt");
        if (File.Exists(promptFile))
        {
            _systemPrompt = File.ReadAllText(promptFile, Encoding.UTF8).Trim();
        }
    }

    public string? SystemPrompt => _systemPrompt;

    /// <summary>사내 서비스(Ollama/vLLM)면 암호화 모드에 따라 복호화, 외부면 그대로 반환.</summary>
    private string ResolveModelName()
    {
        var llm = _settings.Settings.Llm;
        if (llm.Service is "ollama" or "vllm" && !string.IsNullOrEmpty(llm.Model))
            return CryptoService.DecryptIfEnabled(llm.Model, llm.EncryptionEnabled);
        return llm.Model;
    }

    /// <summary>
    /// 현재 활성 모델에 매칭되는 RegisteredModel을 찾아 엔드포인트/API키를 반환합니다.
    /// RegisteredModel에 전용 서버 정보가 있으면 그것을 사용하고, 없으면 기본 설정을 사용합니다.
    /// </summary>
    private (string Endpoint, string ApiKey) ResolveServerInfo()
    {
        var llm = _settings.Settings.Llm;
        var activeService = ResolveService();
        var modelName = ResolveModel();

        // RegisteredModel에서 현재 모델과 서비스가 일치하는 항목 검색
        var registered = FindRegisteredModel(llm, activeService, modelName);

        if (registered != null)
        {
            // 엔드포인트: 등록된 값이 있으면 사용, 없으면 서비스 기본값
            var endpoint = !string.IsNullOrEmpty(registered.Endpoint)
                ? registered.Endpoint
                : GetDefaultEndpoint(llm, activeService);
            // API 키: 등록된 값이 있으면 반드시 사용 — endpoint가 비어있어도 registered.ApiKey 우선
            var apiKey = !string.IsNullOrEmpty(registered.ApiKey)
                ? CryptoService.DecryptIfEnabled(registered.ApiKey, llm.EncryptionEnabled)
                : GetDefaultApiKey(llm, activeService);
            return (endpoint, apiKey);
        }

        // RegisteredModel 없음 — 서비스 기본 엔드포인트 + 기본 API 키
        return activeService.ToLowerInvariant() switch
        {
            "vllm"   => (llm.VllmEndpoint,   CryptoService.DecryptIfEnabled(llm.VllmApiKey,   llm.EncryptionEnabled)),
            "ollama" => (llm.OllamaEndpoint,  CryptoService.DecryptIfEnabled(llm.OllamaApiKey, llm.EncryptionEnabled)),
            _ => ("", ""),
        };
    }

    private static string GetDefaultEndpoint(Models.LlmSettings llm, string service) =>
        service.ToLowerInvariant() switch
        {
            "vllm"   => llm.VllmEndpoint,
            "ollama" => llm.OllamaEndpoint,
            _        => ""
        };

    /// <summary>현재 활성 모델의 RegisteredModel을 찾습니다.</summary>
    private static Models.RegisteredModel? FindRegisteredModel(Models.LlmSettings llm, string service, string modelName)
    {
        return llm.RegisteredModels.FirstOrDefault(m =>
            m.Service.Equals(service, StringComparison.OrdinalIgnoreCase) &&
            (CryptoService.DecryptIfEnabled(m.EncryptedModelName, llm.EncryptionEnabled) == modelName ||
             m.Alias == modelName));
    }

    /// <summary>
    /// 현재 활성 모델의 인증 헤더 값을 반환합니다.
    /// CP4D 인증인 경우 토큰을 자동 발급/캐싱하여 반환합니다.
    /// </summary>
    internal async Task<string?> ResolveAuthTokenAsync(CancellationToken ct = default)
    {
        var llm = _settings.Settings.Llm;
        var activeService = ResolveService();
        var modelName = ResolveModel();
        var registered = FindRegisteredModel(llm, activeService, modelName);

        // CP4D 인증 방식인 경우
        if (registered != null &&
            registered.AuthType.Equals("cp4d", StringComparison.OrdinalIgnoreCase) &&
            !string.IsNullOrWhiteSpace(registered.Cp4dUrl))
        {
            var password = CryptoService.DecryptIfEnabled(registered.Cp4dPassword, llm.EncryptionEnabled);
            var token = await Cp4dTokenService.GetTokenAsync(
                registered.Cp4dUrl, registered.Cp4dUsername, password, ct);
            return token;
        }

        // 기본 Bearer 인증 — 기존 API 키 반환
        var (_, apiKey) = ResolveServerInfo();
        return string.IsNullOrEmpty(apiKey) ? null : apiKey;
    }

    /// <summary>
    /// HttpRequestMessage에 인증 헤더를 적용합니다.
    /// CP4D 인증인 경우 자동 토큰 발급, 일반 Bearer인 경우 API 키를 사용합니다.
    /// </summary>
    private async Task ApplyAuthHeaderAsync(HttpRequestMessage req, CancellationToken ct)
    {
        var token = await ResolveAuthTokenAsync(ct);
        if (!string.IsNullOrEmpty(token))
            req.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
    }

    private static string GetDefaultApiKey(LlmSettings llm, string? service = null)
    {
        var svc = service ?? llm.Service;
        return svc.ToLowerInvariant() switch
        {
            "vllm" => CryptoService.DecryptIfEnabled(llm.VllmApiKey, llm.EncryptionEnabled),
            "ollama" => CryptoService.DecryptIfEnabled(llm.OllamaApiKey, llm.EncryptionEnabled),
            _ => "",
        };
    }

    // ─── 비스트리밍 응답 ───────────────────────────────────────────────────

    public async Task<string> SendAsync(List<ChatMessage> messages, CancellationToken ct = default)
    {
        var llm = _settings.Settings.Llm;
        var activeService = ResolveService();
        try
        {
            return await SendWithServiceAsync(activeService, messages, ct);
        }
        catch (Exception ex) when (llm.FallbackModels.Count > 0)
        {
            // 폴백 모델 순차 시도
            foreach (var fallback in llm.FallbackModels)
            {
                var parts = fallback.Split(':', 2);
                var fbService = parts[0].Trim();
                var fbModel = parts.Length > 1 ? parts[1].Trim() : "";
                try
                {
                    LogService.Warn($"모델 폴백: {activeService} → {fbService} ({ex.Message})");
                    LastFallbackInfo = $"{activeService} → {fbService}";
                    return await SendWithServiceAsync(fbService, messages, ct);
                }
                catch (Exception) { continue; }
            }
            throw; // 모든 폴백 실패
        }
    }

    /// <summary>마지막 폴백 정보 (UI 표시용). null이면 폴백 미발생.</summary>
    public string? LastFallbackInfo { get; private set; }

    private Task<string> SendWithServiceAsync(string service, List<ChatMessage> messages, CancellationToken ct)
    {
        return service.ToLowerInvariant() switch
        {
            "gemini"  => SendGeminiAsync(messages, ct),
            "claude"  => SendClaudeAsync(messages, ct),
            "vllm"    => SendOpenAiCompatibleAsync(messages, ct),
            _         => SendOllamaAsync(messages, ct),
        };
    }

}
