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
{
    // ─── Gemini + Claude 스트리밍 ──────────────────────────────────────────

    // ═══════════════════════════════════════════════════════════════════════
    // Gemini
    // ═══════════════════════════════════════════════════════════════════════

    private async Task<string> SendGeminiAsync(List<ChatMessage> messages, CancellationToken ct)
    {
        var llm    = _settings.Settings.Llm;
        var apiKey = ResolveApiKeyForService("gemini");
        if (string.IsNullOrEmpty(apiKey))
            throw new InvalidOperationException("Gemini API 키가 설정되지 않았습니다. 설정 > AX Agent에서 API 키를 입력하세요.");

        var model  = ResolveModel();
        var body   = BuildGeminiBody(messages);
        var url    = $"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={apiKey}";
        var resp   = await PostJsonWithRetryAsync(url, body, ct);
        return SafeParseJson(resp, root =>
        {
            TryParseGeminiUsage(root);
            var candidates = root.GetProperty("candidates");
            if (candidates.GetArrayLength() == 0) return "(빈 응답)";
            var parts = candidates[0].GetProperty("content").GetProperty("parts");
            if (parts.GetArrayLength() == 0) return "(빈 응답)";
            return parts[0].GetProperty("text").GetString() ?? "";
        }, "Gemini 응답");
    }

    private async IAsyncEnumerable<string> StreamGeminiAsync(
        List<ChatMessage> messages,
        [EnumeratorCancellation] CancellationToken ct)
    {
        var llm    = _settings.Settings.Llm;
        var apiKey = ResolveApiKeyForService("gemini");
        if (string.IsNullOrEmpty(apiKey))
            throw new InvalidOperationException("Gemini API 키가 설정되지 않았습니다.");

        var model  = ResolveModel();
        var body   = BuildGeminiBody(messages);
        var url    = $"https://generativelanguage.googleapis.com/v1beta/models/{model}:streamGenerateContent?alt=sse&key={apiKey}";

        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) || !line.StartsWith("data: ")) continue;
            var data = line["data: ".Length..];
            string? parsed = null;
            try
            {
                using var doc = JsonDocument.Parse(data);
                TryParseGeminiUsage(doc.RootElement);
                var candidates = doc.RootElement.GetProperty("candidates");
                if (candidates.GetArrayLength() == 0) continue;
                var sb = new StringBuilder();
                var parts = candidates[0].GetProperty("content").GetProperty("parts");
                foreach (var part in parts.EnumerateArray())
                {
                    if (part.TryGetProperty("text", out var t))
                    {
                        var text = t.GetString();
                        if (!string.IsNullOrEmpty(text)) sb.Append(text);
                    }
                }
                if (sb.Length > 0) parsed = sb.ToString();
            }
            catch (JsonException ex)
            {
                LogService.Warn($"Gemini 스트리밍 JSON 파싱 오류: {ex.Message}");
            }
            if (parsed != null) yield return parsed;
        }
    }

    private object BuildGeminiBody(List<ChatMessage> messages)
    {
        var llm     = _settings.Settings.Llm;
        var contents = new List<object>();

        object? systemInstruction = null;
        if (!string.IsNullOrEmpty(_systemPrompt))
        {
            systemInstruction = new { parts = new[] { new { text = _systemPrompt } } };
        }

        foreach (var m in messages)
        {
            if (m.Role == "system") continue;
            var parts = new List<object> { new { text = m.Content } };
            if (m.Images?.Count > 0)
            {
                foreach (var img in m.Images)
                    parts.Add(new { inlineData = new { mimeType = img.MimeType, data = img.Base64 } });
            }
            contents.Add(new
            {
                role = m.Role == "assistant" ? "model" : "user",
                parts
            });
        }

        if (systemInstruction != null)
            return new
            {
                systemInstruction,
                contents,
                generationConfig = new { temperature = llm.Temperature, maxOutputTokens = llm.MaxContextTokens }
            };

        return new
        {
            contents,
            generationConfig = new { temperature = llm.Temperature, maxOutputTokens = llm.MaxContextTokens }
        };
    }

    // ═══════════════════════════════════════════════════════════════════════
    // Claude (Anthropic Messages API)
    // ═══════════════════════════════════════════════════════════════════════

    private async Task<string> SendClaudeAsync(List<ChatMessage> messages, CancellationToken ct)
    {
        var llm = _settings.Settings.Llm;
        var apiKey = llm.ApiKey;
        if (string.IsNullOrEmpty(apiKey))
            throw new InvalidOperationException("Claude API 키가 설정되지 않았습니다. 설정 > AX Agent에서 API 키를 입력하세요.");

        var body = BuildClaudeBody(messages, stream: false);
        var json = JsonSerializer.Serialize(body);
        using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.anthropic.com/v1/messages");
        req.Content = new StringContent(json, Encoding.UTF8, "application/json");
        req.Headers.Add("x-api-key", apiKey);
        req.Headers.Add("anthropic-version", "2023-06-01");

        using var resp = await _http.SendAsync(req, ct);
        if (!resp.IsSuccessStatusCode)
        {
            var errBody = await resp.Content.ReadAsStringAsync(ct);
            throw new HttpRequestException(ClassifyHttpError(resp, errBody));
        }

        var respJson = await resp.Content.ReadAsStringAsync(ct);
        return SafeParseJson(respJson, root =>
        {
            TryParseClaudeUsage(root);
            var content = root.GetProperty("content");
            if (content.GetArrayLength() == 0) return "(빈 응답)";
            return content[0].GetProperty("text").GetString() ?? "";
        }, "Claude 응답");
    }

    private async IAsyncEnumerable<string> StreamClaudeAsync(
        List<ChatMessage> messages,
        [EnumeratorCancellation] CancellationToken ct)
    {
        var llm = _settings.Settings.Llm;
        var apiKey = llm.ApiKey;
        if (string.IsNullOrEmpty(apiKey))
            throw new InvalidOperationException("Claude API 키가 설정되지 않았습니다.");

        var body = BuildClaudeBody(messages, stream: true);
        var json = JsonSerializer.Serialize(body);
        using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.anthropic.com/v1/messages");
        req.Content = new StringContent(json, Encoding.UTF8, "application/json");
        req.Headers.Add("x-api-key", apiKey);
        req.Headers.Add("anthropic-version", "2023-06-01");

        using var resp = await _http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct);
        if (!resp.IsSuccessStatusCode)
        {
            var errBody = await resp.Content.ReadAsStringAsync(ct);
            throw new HttpRequestException(ClassifyHttpError(resp, errBody));
        }

        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..];

            string? text = null;
            try
            {
                using var doc = JsonDocument.Parse(data);
                var type = doc.RootElement.GetProperty("type").GetString();
                if (type == "content_block_delta")
                {
                    var delta = doc.RootElement.GetProperty("delta");
                    if (delta.TryGetProperty("text", out var t))
                        text = t.GetString();
                }
                else if (type is "message_start" or "message_delta")
                {
                    // message_start: usage in .message.usage, message_delta: usage in .usage
                    if (doc.RootElement.TryGetProperty("message", out var msg) &&
                        msg.TryGetProperty("usage", out var u1))
                        TryParseClaudeUsageFromElement(u1);
                    else if (doc.RootElement.TryGetProperty("usage", out var u2))
                        TryParseClaudeUsageFromElement(u2);
                }
            }
            catch (JsonException ex)
            {
                LogService.Warn($"Claude 스트리밍 JSON 파싱 오류: {ex.Message}");
            }
            if (!string.IsNullOrEmpty(text)) yield return text;
        }
    }

    private object BuildClaudeBody(List<ChatMessage> messages, bool stream)
    {
        var llm = _settings.Settings.Llm;
        var msgs = new List<object>();

        foreach (var m in messages)
        {
            if (m.Role == "system") continue;
            if (m.Images?.Count > 0)
            {
                // Claude Vision: content를 배열로 변환 (이미지 + 텍스트)
                var contentParts = new List<object>();
                foreach (var img in m.Images)
                    contentParts.Add(new { type = "image", source = new { type = "base64", media_type = img.MimeType, data = img.Base64 } });
                contentParts.Add(new { type = "text", text = m.Content });
                msgs.Add(new { role = m.Role, content = contentParts });
            }
            else
            {
                msgs.Add(new { role = m.Role, content = m.Content });
            }
        }

        var activeModel = ResolveModel();
        if (!string.IsNullOrEmpty(_systemPrompt))
        {
            return new
            {
                model = activeModel,
                max_tokens = llm.MaxContextTokens,
                temperature = llm.Temperature,
                system = _systemPrompt,
                messages = msgs,
                stream
            };
        }

        return new
        {
            model = activeModel,
            max_tokens = llm.MaxContextTokens,
            temperature = llm.Temperature,
            messages = msgs,
            stream
        };
    }
}
