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

namespace AxCopilot.Services;

public partial class LlmService
{
    // ─── 스트리밍 응답 ────────────────────────────────────────────────────

    public async IAsyncEnumerable<string> StreamAsync(
        List<ChatMessage> messages,
        [EnumeratorCancellation] CancellationToken ct = default)
    {
        var activeService = ResolveService();
        var stream = activeService.ToLowerInvariant() switch
        {
            "gemini"  => StreamGeminiAsync(messages, ct),
            "claude"  => StreamClaudeAsync(messages, ct),
            "vllm"    => StreamOpenAiCompatibleAsync(messages, ct),
            _         => StreamOllamaAsync(messages, ct),
        };
        await foreach (var chunk in stream.WithCancellation(ct))
            yield return chunk;
    }

    // ─── 연결 테스트 ──────────────────────────────────────────────────────

    public async Task<(bool ok, string message)> TestConnectionAsync()
    {
        try
        {
            var llm = _settings.Settings.Llm;
            switch (llm.Service.ToLowerInvariant())
            {
                case "ollama":
                    var resp = await _http.GetAsync(llm.Endpoint.TrimEnd('/') + "/api/tags");
                    return resp.IsSuccessStatusCode
                        ? (true, "Ollama 연결 성공")
                        : (false, ClassifyHttpError(resp));

                case "vllm":
                    var vResp = await _http.GetAsync(llm.Endpoint.TrimEnd('/') + "/v1/models");
                    return vResp.IsSuccessStatusCode
                        ? (true, "vLLM 연결 성공")
                        : (false, ClassifyHttpError(vResp));

                case "gemini":
                    var gKey = llm.ApiKey;
                    if (string.IsNullOrEmpty(gKey)) return (false, "API 키가 설정되지 않았습니다");
                    var gResp = await _http.GetAsync(
                        $"https://generativelanguage.googleapis.com/v1beta/models?key={gKey}");
                    return gResp.IsSuccessStatusCode
                        ? (true, "Gemini API 연결 성공")
                        : (false, ClassifyHttpError(gResp));

                case "claude":
                {
                    var cKey = llm.ApiKey;
                    if (string.IsNullOrEmpty(cKey)) return (false, "API 키가 설정되지 않았습니다");
                    using var cReq = new HttpRequestMessage(HttpMethod.Get, "https://api.anthropic.com/v1/models");
                    cReq.Headers.Add("x-api-key", cKey);
                    cReq.Headers.Add("anthropic-version", "2023-06-01");
                    var cResp = await _http.SendAsync(cReq);
                    return cResp.IsSuccessStatusCode
                        ? (true, "Claude API 연결 성공")
                        : (false, ClassifyHttpError(cResp));
                }

                default:
                    return (false, "알 수 없는 서비스");
            }
        }
        catch (TaskCanceledException)
        {
            return (false, "연결 시간 초과 — 서버가 응답하지 않습니다");
        }
        catch (HttpRequestException ex)
        {
            return (false, $"연결 실패 — {ex.Message}");
        }
        catch (Exception ex)
        {
            return (false, ex.Message);
        }
    }

    // ═══════════════════════════════════════════════════════════════════════
    // Ollama
    // ═══════════════════════════════════════════════════════════════════════

    private async Task<string> SendOllamaAsync(List<ChatMessage> messages, CancellationToken ct)
    {
        var llm  = _settings.Settings.Llm;
        var (endpoint, _) = ResolveServerInfo();
        var ep = string.IsNullOrEmpty(endpoint) ? llm.Endpoint : endpoint;
        var body = BuildOllamaBody(messages, stream: false);
        var resp = await PostJsonWithRetryAsync(ep.TrimEnd('/') + "/api/chat", body, ct);
        return SafeParseJson(resp, root =>
        {
            TryParseOllamaUsage(root);
            return root.GetProperty("message").GetProperty("content").GetString() ?? "";
        }, "Ollama 응답");
    }

    private async IAsyncEnumerable<string> StreamOllamaAsync(
        List<ChatMessage> messages,
        [EnumeratorCancellation] CancellationToken ct)
    {
        var llm  = _settings.Settings.Llm;
        var (endpoint, _) = ResolveServerInfo();
        var ep = string.IsNullOrEmpty(endpoint) ? llm.Endpoint : endpoint;
        var body = BuildOllamaBody(messages, stream: true);
        var url  = ep.TrimEnd('/') + "/api/chat";

        using var req  = new HttpRequestMessage(HttpMethod.Post, url) { Content = JsonContent(body) };
        using var resp = await SendWithErrorClassificationAsync(req, ct);

        using var stream = await resp.Content.ReadAsStreamAsync(ct);
        using var reader = new StreamReader(stream);

        while (!reader.EndOfStream && !ct.IsCancellationRequested)
        {
            var line = await ReadLineWithTimeoutAsync(reader, ct);
            if (line == null) break;
            if (string.IsNullOrEmpty(line)) continue;

            string? text = null;
            try
            {
                using var doc = JsonDocument.Parse(line);
                if (doc.RootElement.TryGetProperty("message", out var msg) &&
                    msg.TryGetProperty("content", out var c))
                    text = c.GetString();
                // Ollama: done=true 시 토큰 사용량 포함
                if (doc.RootElement.TryGetProperty("done", out var done) && done.GetBoolean())
                    TryParseOllamaUsage(doc.RootElement);
            }
            catch (JsonException ex)
            {
                LogService.Warn($"Ollama 스트리밍 JSON 파싱 오류: {ex.Message}");
            }
            if (!string.IsNullOrEmpty(text)) yield return text;
        }
    }

    private object BuildOllamaBody(List<ChatMessage> messages, bool stream)
    {
        var llm = _settings.Settings.Llm;
        var msgs = BuildMessageList(messages);
        return new
        {
            model = ResolveModelName(),
            messages = msgs,
            stream = stream,
            options = new { temperature = llm.Temperature }
        };
    }

    // ═══════════════════════════════════════════════════════════════════════
    // OpenAI-Compatible (vLLM)
    // ═══════════════════════════════════════════════════════════════════════

    private async Task<string> SendOpenAiCompatibleAsync(List<ChatMessage> messages, CancellationToken ct)
    {
        var llm  = _settings.Settings.Llm;
        var (endpoint, _) = ResolveServerInfo();
        var ep = string.IsNullOrEmpty(endpoint) ? llm.Endpoint : endpoint;
        var body = BuildOpenAiBody(messages, stream: false);
        var url  = ep.TrimEnd('/') + "/v1/chat/completions";
        var json = JsonSerializer.Serialize(body);

        using var req = new HttpRequestMessage(HttpMethod.Post, url)
        {
            Content = new StringContent(json, Encoding.UTF8, "application/json")
        };
        await ApplyAuthHeaderAsync(req, ct);

        using var resp = await SendWithErrorClassificationAsync(req, ct);
        var respBody = await resp.Content.ReadAsStringAsync(ct);
        return SafeParseJson(respBody, root =>
        {
            TryParseOpenAiUsage(root);
            var choices = root.GetProperty("choices");
            if (choices.GetArrayLength() == 0) return "(빈 응답)";
            return choices[0].GetProperty("message").GetProperty("content").GetString() ?? "";
        }, "vLLM 응답");
    }

    private async IAsyncEnumerable<string> StreamOpenAiCompatibleAsync(
        List<ChatMessage> messages,
        [EnumeratorCancellation] CancellationToken ct)
    {
        var llm  = _settings.Settings.Llm;
        var (endpoint, _) = ResolveServerInfo();
        var ep = string.IsNullOrEmpty(endpoint) ? llm.Endpoint : endpoint;
        var body = BuildOpenAiBody(messages, stream: true);
        var url  = ep.TrimEnd('/') + "/v1/chat/completions";

        using var req  = new HttpRequestMessage(HttpMethod.Post, url) { Content = JsonContent(body) };
        await ApplyAuthHeaderAsync(req, ct);
        using var resp = await SendWithErrorClassificationAsync(req, ct);

        using var stream = await resp.Content.ReadAsStreamAsync(ct);
        using var reader = new StreamReader(stream);

        while (!reader.EndOfStream && !ct.IsCancellationRequested)
        {
            var line = await ReadLineWithTimeoutAsync(reader, ct);
            if (line == null) break;
            if (string.IsNullOrEmpty(line) || !line.StartsWith("data: ")) continue;
            var data = line["data: ".Length..];
            if (data == "[DONE]") break;

            string? text = null;
            try
            {
                using var doc = JsonDocument.Parse(data);
                TryParseOpenAiUsage(doc.RootElement);
                var choices = doc.RootElement.GetProperty("choices");
                if (choices.GetArrayLength() > 0)
                {
                    var delta = choices[0].GetProperty("delta");
                    if (delta.TryGetProperty("content", out var c))
                        text = c.GetString();
                }
            }
            catch (JsonException ex)
            {
                LogService.Warn($"vLLM 스트리밍 JSON 파싱 오류: {ex.Message}");
            }
            if (!string.IsNullOrEmpty(text)) yield return text;
        }
    }

    private object BuildOpenAiBody(List<ChatMessage> messages, bool stream)
    {
        var llm = _settings.Settings.Llm;
        var msgs = BuildMessageList(messages, openAiVision: true);
        return new
        {
            model = ResolveModelName(),
            messages = msgs,
            stream = stream,
            temperature = llm.Temperature,
            max_tokens = llm.MaxContextTokens
        };
    }

}
