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

namespace AxCopilot.Services;

public partial class LlmService
{
    // ─── 공용 헬퍼 ─────────────────────────────────────────────────────────

    private List<object> BuildMessageList(List<ChatMessage> messages, bool openAiVision = false)
    {
        var result = new List<object>();
        if (!string.IsNullOrEmpty(_systemPrompt))
            result.Add(new { role = "system", content = _systemPrompt });

        foreach (var m in messages)
        {
            if (m.Role == "system") continue;
            if (m.Images?.Count > 0)
            {
                if (openAiVision)
                {
                    // OpenAI Vision: content 배열 (text + image_url)
                    var contentParts = new List<object>();
                    contentParts.Add(new { type = "text", text = m.Content });
                    foreach (var img in m.Images)
                        contentParts.Add(new { type = "image_url", image_url = new { url = $"data:{img.MimeType};base64,{img.Base64}" } });
                    result.Add(new { role = m.Role, content = contentParts });
                }
                else
                {
                    // Ollama Vision: images 필드에 base64 배열
                    result.Add(new { role = m.Role, content = m.Content, images = m.Images.Select(i => i.Base64).ToArray() });
                }
            }
            else
            {
                result.Add(new { role = m.Role, content = m.Content });
            }
        }
        return result;
    }

    /// <summary>비스트리밍 POST + 재시도 (일시적 오류 시 최대 2회)</summary>
    private async Task<string> PostJsonWithRetryAsync(string url, object body, CancellationToken ct)
    {
        var json = JsonSerializer.Serialize(body);
        Exception? lastEx = null;

        for (int attempt = 0; attempt <= MaxRetries; attempt++)
        {
            try
            {
                using var content = new StringContent(json, Encoding.UTF8, "application/json");
                using var resp = await _http.PostAsync(url, content, ct);

                if (resp.IsSuccessStatusCode)
                    return await resp.Content.ReadAsStringAsync(ct);

                // 429 Rate Limit → 재시도
                if ((int)resp.StatusCode == 429 && attempt < MaxRetries)
                {
                    await Task.Delay(1000 * (attempt + 1), ct);
                    continue;
                }

                // 그 외 에러 → 분류 후 예외
                var errBody = await resp.Content.ReadAsStringAsync(ct);
                throw new HttpRequestException(ClassifyHttpError(resp, errBody));
            }
            catch (HttpRequestException) { throw; }
            catch (TaskCanceledException) when (!ct.IsCancellationRequested && attempt < MaxRetries)
            {
                lastEx = new TimeoutException("요청 시간 초과");
                await Task.Delay(1000 * (attempt + 1), ct);
            }
        }
        throw lastEx ?? new HttpRequestException("요청 실패");
    }

    /// <summary>스트리밍 전용 — HTTP 요청 전송 + 에러 분류</summary>
    private async Task<HttpResponseMessage> SendWithErrorClassificationAsync(
        HttpRequestMessage req, CancellationToken ct)
    {
        var resp = await _http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct);
        if (!resp.IsSuccessStatusCode)
        {
            var errBody = await resp.Content.ReadAsStringAsync(ct);
            var errorMsg = ClassifyHttpError(resp, errBody);
            resp.Dispose();
            throw new HttpRequestException(errorMsg);
        }
        return resp;
    }

    /// <summary>스트리밍 ReadLine에 청크 타임아웃 적용</summary>
    private static async Task<string?> ReadLineWithTimeoutAsync(StreamReader reader, CancellationToken ct)
    {
        using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
        cts.CancelAfter(ChunkTimeout);
        try
        {
            return await reader.ReadLineAsync(cts.Token);
        }
        catch (OperationCanceledException) when (!ct.IsCancellationRequested)
        {
            LogService.Warn("스트리밍 청크 타임아웃 (30초 무응답)");
            return null; // 타임아웃 시 스트림 종료
        }
    }

    /// <summary>JSON 파싱 안전 래퍼 — 파싱 실패 시 상세 에러 메시지 반환</summary>
    private static string SafeParseJson(string json, Func<JsonElement, string> extractor, string context)
    {
        try
        {
            using var doc = JsonDocument.Parse(json);

            // API 에러 응답 감지
            if (doc.RootElement.TryGetProperty("error", out var error))
            {
                var msg = error.TryGetProperty("message", out var m) ? m.GetString() : error.ToString();
                throw new HttpRequestException($"[{context}] API 에러: {msg}");
            }

            return extractor(doc.RootElement);
        }
        catch (JsonException ex)
        {
            var preview = json.Length > 200 ? json[..200] + "…" : json;
            throw new InvalidOperationException(
                $"[{context}] 응답 형식 오류 — 예상하지 못한 JSON 형식입니다.\n파싱 오류: {ex.Message}\n응답 미리보기: {preview}");
        }
        catch (KeyNotFoundException)
        {
            var preview = json.Length > 200 ? json[..200] + "…" : json;
            throw new InvalidOperationException(
                $"[{context}] 응답에 필요한 필드가 없습니다.\n응답 미리보기: {preview}");
        }
    }

    /// <summary>HTTP 에러 코드별 사용자 친화적 메시지</summary>
    private static string ClassifyHttpError(HttpResponseMessage resp, string? body = null)
    {
        var code = (int)resp.StatusCode;
        var detail = "";

        // JSON error.message 추출 시도
        if (!string.IsNullOrEmpty(body))
        {
            try
            {
                using var doc = JsonDocument.Parse(body);
                if (doc.RootElement.TryGetProperty("error", out var err))
                {
                    if (err.ValueKind == JsonValueKind.Object && err.TryGetProperty("message", out var m))
                        detail = m.GetString() ?? "";
                    else if (err.ValueKind == JsonValueKind.String)
                        detail = err.GetString() ?? "";
                }
            }
            catch (Exception) { }
        }

        var msg = code switch
        {
            400 => "잘못된 요청 — 모델 이름이나 요청 형식을 확인하세요",
            401 => "인증 실패 — API 키가 유효하지 않습니다",
            403 => "접근 거부 — API 키 권한을 확인하세요",
            404 => "모델을 찾을 수 없습니다 — 모델 이름을 확인하세요",
            429 => "요청 한도 초과 — 잠시 후 다시 시도하세요",
            500 => "서버 내부 오류 — LLM 서버 상태를 확인하세요",
            502 or 503 => "서버 일시 장애 — 잠시 후 다시 시도하세요",
            _ => $"HTTP {code} 오류"
        };

        return string.IsNullOrEmpty(detail) ? msg : $"{msg}\n상세: {detail}";
    }

    private static StringContent JsonContent(object body)
    {
        var json = JsonSerializer.Serialize(body);
        return new StringContent(json, Encoding.UTF8, "application/json");
    }

    // ─── 토큰 사용량 파싱 헬퍼 ──────────────────────────────────────────

    private void TryParseOllamaUsage(JsonElement root)
    {
        try
        {
            var prompt = root.TryGetProperty("prompt_eval_count", out var p) ? p.GetInt32() : 0;
            var completion = root.TryGetProperty("eval_count", out var e) ? e.GetInt32() : 0;
            if (prompt > 0 || completion > 0)
                LastTokenUsage = new TokenUsage(prompt, completion);
        }
        catch (Exception) { }
    }

    private void TryParseOpenAiUsage(JsonElement root)
    {
        try
        {
            if (!root.TryGetProperty("usage", out var usage)) return;
            var prompt = usage.TryGetProperty("prompt_tokens", out var p) ? p.GetInt32() : 0;
            var completion = usage.TryGetProperty("completion_tokens", out var c) ? c.GetInt32() : 0;
            if (prompt > 0 || completion > 0)
                LastTokenUsage = new TokenUsage(prompt, completion);
        }
        catch (Exception) { }
    }

    private void TryParseGeminiUsage(JsonElement root)
    {
        try
        {
            if (!root.TryGetProperty("usageMetadata", out var usage)) return;
            var prompt = usage.TryGetProperty("promptTokenCount", out var p) ? p.GetInt32() : 0;
            var completion = usage.TryGetProperty("candidatesTokenCount", out var c) ? c.GetInt32() : 0;
            if (prompt > 0 || completion > 0)
                LastTokenUsage = new TokenUsage(prompt, completion);
        }
        catch (Exception) { }
    }

    private void TryParseClaudeUsage(JsonElement root)
    {
        try
        {
            if (!root.TryGetProperty("usage", out var usage)) return;
            TryParseClaudeUsageFromElement(usage);
        }
        catch (Exception) { }
    }

    private void TryParseClaudeUsageFromElement(JsonElement usage)
    {
        try
        {
            var input = usage.TryGetProperty("input_tokens", out var i) ? i.GetInt32() : 0;
            var output = usage.TryGetProperty("output_tokens", out var o) ? o.GetInt32() : 0;
            if (input > 0 || output > 0)
                LastTokenUsage = new TokenUsage(input, output);
        }
        catch (Exception) { }
    }

    public void Dispose() => _http.Dispose();
}
