using System.IO;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using AxCopilot.Models;

namespace AxCopilot.Services.Agent;

/// <summary>반성 메모리 엔트리.</summary>
public record ReflexionEntry
{
    [JsonPropertyName("task_type")]
    public string TaskType { get; init; } = "";

    [JsonPropertyName("summary")]
    public string Summary { get; init; } = "";

    [JsonPropertyName("is_success")]
    public bool IsSuccess { get; init; }

    [JsonPropertyName("score")]
    public float CompletionScore { get; init; }

    [JsonPropertyName("weaknesses")]
    public string[] Weaknesses { get; init; } = Array.Empty<string>();

    [JsonPropertyName("strengths")]
    public string[] Strengths { get; init; } = Array.Empty<string>();

    [JsonPropertyName("lessons")]
    public string[] Lessons { get; init; } = Array.Empty<string>();

    [JsonPropertyName("created_at")]
    public DateTime CreatedAt { get; init; } = DateTime.UtcNow;

    [JsonPropertyName("session_id")]
    public string SessionId { get; init; } = "";
}

/// <summary>
/// 반성 메모리 저장소.
/// 저장: %APPDATA%\AxCopilot\reflexion\{taskType}.jsonl
/// </summary>
public class ReflexionRepository
{
    private static readonly string BaseDir = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
        "AxCopilot", "reflexion");

    private static readonly JsonSerializerOptions _json = new()
    {
        Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
    };

    public async Task SaveAsync(ReflexionEntry entry)
    {
        Directory.CreateDirectory(BaseDir);
        var path = GetPath(entry.TaskType);
        var line = JsonSerializer.Serialize(entry, _json);
        await File.AppendAllTextAsync(path, line + "\n");
    }

    public async Task<IReadOnlyList<ReflexionEntry>> GetByTaskTypeAsync(string taskType, int limit = 5)
    {
        var path = GetPath(taskType);
        if (!File.Exists(path)) return Array.Empty<ReflexionEntry>();

        var lines = await File.ReadAllLinesAsync(path);
        var results = new List<ReflexionEntry>();
        foreach (var line in lines.Reverse())
        {
            if (string.IsNullOrWhiteSpace(line)) continue;
            try
            {
                var entry = JsonSerializer.Deserialize<ReflexionEntry>(line, _json);
                if (entry != null) results.Add(entry);
                if (results.Count >= limit) break;
            }
            catch (Exception) { }
        }
        return results;
    }

    /// <summary>동일 작업 유형의 과거 교훈을 시스템 프롬프트 주입용 텍스트로 빌드.</summary>
    public async Task<string> BuildContextPromptAsync(string taskType, int maxEntries = 5)
    {
        var entries = await GetByTaskTypeAsync(taskType, maxEntries);
        if (entries.Count == 0) return string.Empty;

        var sb = new StringBuilder();
        sb.AppendLine($"\n## 이전 유사 작업({taskType})에서 배운 점:");
        foreach (var e in entries)
        {
            if (e.Lessons.Length > 0)
                foreach (var lesson in e.Lessons)
                    sb.AppendLine($"- {lesson}");
            if (!e.IsSuccess && e.Weaknesses.Length > 0)
                foreach (var w in e.Weaknesses)
                    sb.AppendLine($"- 주의: {w}");
        }
        return sb.ToString();
    }

    private static string GetPath(string taskType)
    {
        var safe = string.Concat(taskType.Where(c => char.IsLetterOrDigit(c) || c == '_' || c == '-'));
        return Path.Combine(BaseDir, $"{safe}.jsonl");
    }
}

/// <summary>작업 유형 분류기. 사용자 입력 → 작업 유형 분류.</summary>
public static class TaskTypeClassifier
{
    private static readonly Dictionary<string, string[]> _keywords = new()
    {
        ["code_generation"] = new[] { "만들어", "생성해", "작성해", "구현해", ".cs", ".py", ".js", ".ts", "class", "function", "함수", "클래스" },
        ["file_refactor"]   = new[] { "리팩터", "refactor", "개선해", "최적화", "정리해", "수정해" },
        ["document"]        = new[] { "문서", "보고서", "report", "document", ".docx", ".xlsx", ".html", ".pdf", "작성해", "만들어" },
        ["analysis"]        = new[] { "분석", "analyze", "분석해", "검토해", "확인해", "review" },
        ["search"]          = new[] { "검색", "찾아", "search", "find", "grep", "어디" },
        ["test"]            = new[] { "테스트", "test", "단위", "unit", "검증", "verify" },
        ["git"]             = new[] { "commit", "push", "pull", "merge", "branch", "git" },
        ["debug"]           = new[] { "버그", "bug", "오류", "error", "fix", "수정" },
    };

    public static string Classify(string userMessage)
    {
        var lower = userMessage.ToLowerInvariant();
        var best = "general";
        var bestScore = 0;

        foreach (var (type, keywords) in _keywords)
        {
            var score = keywords.Count(k => lower.Contains(k));
            if (score > bestScore) { bestScore = score; best = type; }
        }
        return best;
    }
}

/// <summary>
/// Phase 17-A: LLM 기반 자기평가 서비스.
/// 세션 완료 후 LLM을 호출해 완성도·강점·약점·교훈을 추출하고 ReflexionEntry를 생성합니다.
/// </summary>
public class ReflexionEvaluatorService
{
    private readonly LlmService _llm;
    private readonly ReflexionRepository _repo;

    private static readonly JsonSerializerOptions _jsonOpts = new()
    {
        Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
        PropertyNameCaseInsensitive = true,
    };

    public ReflexionEvaluatorService(LlmService llm, ReflexionRepository repo)
    {
        _llm = llm;
        _repo = repo;
    }

    /// <summary>
    /// 세션을 평가하고 ReflexionEntry를 저장합니다.
    /// 내부 LLM 호출 실패 시 규칙 기반 폴백 엔트리를 저장합니다.
    /// </summary>
    public async Task EvaluateAndSaveAsync(
        string userQuery,
        string agentResult,
        string sessionId,
        bool isSuccess,
        int toolCallCount,
        int iterationCount,
        CancellationToken ct = default)
    {
        if (string.IsNullOrWhiteSpace(userQuery)) return;

        var taskType = TaskTypeClassifier.Classify(userQuery);

        try
        {
            var entry = await EvaluateWithLlmAsync(
                userQuery, agentResult, taskType, sessionId, isSuccess, toolCallCount, iterationCount, ct);
            await _repo.SaveAsync(entry);
        }
        catch (Exception)
        {
            // LLM 평가 실패 → 규칙 기반 폴백 엔트리
            var fallback = BuildFallbackEntry(userQuery, taskType, sessionId, isSuccess, iterationCount);
            try { await _repo.SaveAsync(fallback); } catch (Exception) { /* 저장 실패도 무시 */ }
        }
    }

    private async Task<ReflexionEntry> EvaluateWithLlmAsync(
        string userQuery,
        string agentResult,
        string taskType,
        string sessionId,
        bool isSuccess,
        int toolCallCount,
        int iterationCount,
        CancellationToken ct)
    {
        var resultPreview = agentResult.Length > 500 ? agentResult[..500] + "…" : agentResult;
        var evalPrompt = $$"""
            에이전트가 다음 작업을 수행했습니다. 수행 결과를 평가하고 JSON으로 응답하세요.

            ## 사용자 요청
            {{userQuery}}

            ## 에이전트 결과 요약
            {{resultPreview}}

            ## 수행 통계
            - 성공 여부: {{(isSuccess ? "성공" : "실패 또는 중단")}}
            - 도구 호출 횟수: {{toolCallCount}}
            - 반복 횟수: {{iterationCount}}

            ## 응답 형식 (JSON만 출력, 다른 텍스트 없이)
            {
              "score": 0.0~1.0,
              "strengths": ["잘된 점1", "잘된 점2"],
              "weaknesses": ["부족한 점1", "부족한 점2"],
              "lessons": ["다음에 적용할 교훈1", "다음에 적용할 교훈2"],
              "summary": "한 줄 요약"
            }
            """;

        var messages = new List<ChatMessage>
        {
            new() { Role = "user", Content = evalPrompt }
        };

        var response = await _llm.SendAsync(messages, ct);

        // JSON 블록 추출
        var json = ExtractJson(response);
        if (string.IsNullOrEmpty(json))
            return BuildFallbackEntry(userQuery, taskType, sessionId, isSuccess, iterationCount);

        var parsed = JsonSerializer.Deserialize<EvalResponse>(json, _jsonOpts);
        if (parsed == null)
            return BuildFallbackEntry(userQuery, taskType, sessionId, isSuccess, iterationCount);

        return new ReflexionEntry
        {
            TaskType        = taskType,
            Summary         = parsed.Summary ?? userQuery[..Math.Min(80, userQuery.Length)],
            IsSuccess       = isSuccess,
            CompletionScore = Math.Clamp(parsed.Score, 0f, 1f),
            Strengths       = parsed.Strengths  ?? Array.Empty<string>(),
            Weaknesses      = parsed.Weaknesses ?? Array.Empty<string>(),
            Lessons         = parsed.Lessons    ?? Array.Empty<string>(),
            CreatedAt       = DateTime.UtcNow,
            SessionId       = sessionId,
        };
    }

    private static ReflexionEntry BuildFallbackEntry(
        string userQuery, string taskType, string sessionId, bool isSuccess, int iterationCount)
    {
        return new ReflexionEntry
        {
            TaskType        = taskType,
            Summary         = userQuery.Length > 80 ? userQuery[..80] + "…" : userQuery,
            IsSuccess       = isSuccess,
            CompletionScore = isSuccess ? 0.7f : 0.3f,
            Strengths       = isSuccess ? new[] { "작업 완료" } : Array.Empty<string>(),
            Weaknesses      = !isSuccess ? new[] { $"반복 {iterationCount}회 후 완료 못함" } : Array.Empty<string>(),
            Lessons         = Array.Empty<string>(),
            CreatedAt       = DateTime.UtcNow,
            SessionId       = sessionId,
        };
    }

    private static string ExtractJson(string text)
    {
        var start = text.IndexOf('{');
        var end   = text.LastIndexOf('}');
        if (start < 0 || end <= start) return string.Empty;
        return text[start..(end + 1)];
    }

    // JSON 응답 역직렬화용 내부 DTO
    private class EvalResponse
    {
        [JsonPropertyName("score")]
        public float Score { get; set; }

        [JsonPropertyName("strengths")]
        public string[]? Strengths { get; set; }

        [JsonPropertyName("weaknesses")]
        public string[]? Weaknesses { get; set; }

        [JsonPropertyName("lessons")]
        public string[]? Lessons { get; set; }

        [JsonPropertyName("summary")]
        public string? Summary { get; set; }
    }
}
