using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Themes;

namespace AxCopilot.Handlers;

/// <summary>
/// L21-2: 로그 파일 분석기 핸들러. "log" 프리픽스로 사용합니다.
///
/// 예: log                    → 클립보드 로그 통계
///     log <경로>             → 파일 경로 직접 분석
///     log error              → ERROR 줄만 표시
///     log warn               → WARN 줄만 표시
///     log last 20            → 마지막 20줄 (tail)
///     log head 20            → 처음 20줄
///     log grep <키워드>      → 키워드 필터
///     log stats              → 레벨별 통계 + 시간대 분포
///     log exceptions         → 예외·스택트레이스 블록 추출
///     log today              → 오늘 날짜 포함 줄만 표시
/// Enter → 값 복사.
/// </summary>
public partial class LogHandler : IActionHandler
{
    public string? Prefix => "log";

    public PluginMetadata Metadata => new(
        "Log",
        "로그 파일 분석기 — ERROR/WARN 필터·tail·grep·통계·예외 추출",
        "1.0",
        "AX");

    private record LogLine(int No, string Level, string Raw);

    // 로그 레벨 감지 패턴
    private static readonly (string Level, string[] Keywords)[] LevelPatterns =
    [
        ("ERROR",   ["[ERROR]", "ERROR:", "ERRO ", "error", "Error", "FATAL", "[FATAL]", "Exception", "EXCEPTION"]),
        ("WARN",    ["[WARN]",  "WARN:",  "WARNING", "warning", "Warning", "[WARNING]"]),
        ("INFO",    ["[INFO]",  "INFO:",  "information", "Information"]),
        ("DEBUG",   ["[DEBUG]", "DEBUG:", "debug", "Debug", "TRACE", "[TRACE]"]),
    ];

    public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
    {
        var q     = query.Trim();
        var items = new List<LauncherItem>();

        // 클립보드 or 파일
        string? src = null;
        string? srcLabel = null;
        try
        {
            System.Windows.Application.Current.Dispatcher.Invoke(() =>
            {
                if (Clipboard.ContainsText()) src = Clipboard.GetText();
            });
        }
        catch { }

        if (string.IsNullOrWhiteSpace(q))
        {
            if (string.IsNullOrWhiteSpace(src))
            {
                items.Add(new LauncherItem("로그 파일 분석기",
                    "클립보드에 로그를 복사하거나 log <파일경로>  /  log error  /  log grep <키워드>",
                    null, null, Symbol: "\uE9D9"));
                return Task.FromResult<IEnumerable<LauncherItem>>(items);
            }
            srcLabel = "클립보드";
            BuildSummary(items, src!, srcLabel);
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        var parts = q.Split(' ', StringSplitOptions.RemoveEmptyEntries);
        var sub   = parts[0].ToLowerInvariant();

        // 파일 경로인지 판단
        if ((sub.Contains('\\') || sub.Contains('/') || sub.Contains(':') || sub.EndsWith(".log") || sub.EndsWith(".txt"))
            && File.Exists(parts[0]))
        {
            string? fileErr = null;
            string? content = null;
            try { content = File.ReadAllText(parts[0]); }
            catch (Exception ex) { fileErr = ex.Message; }

            if (fileErr != null) { items.Add(ErrorItem($"파일 읽기 오류: {fileErr}")); return Task.FromResult<IEnumerable<LauncherItem>>(items); }
            src      = content;
            srcLabel = Path.GetFileName(parts[0]);
            if (parts.Length == 1)
            {
                BuildSummary(items, src!, srcLabel);
                return Task.FromResult<IEnumerable<LauncherItem>>(items);
            }
            // 파일 경로 + 서브커맨드
            sub   = parts[1].ToLowerInvariant();
            parts = parts[1..];
        }
        else
        {
            srcLabel = "클립보드";
        }

        var logSrc = src ?? "";
        if (string.IsNullOrWhiteSpace(logSrc))
        {
            items.Add(ErrorItem("분석할 로그가 없습니다. 클립보드에 로그를 복사하세요."));
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        var allLines = ParseLogLines(logSrc);

        switch (sub)
        {
            case "error" or "err":
                BuildFilteredLines(items, allLines, "ERROR", "ERROR / FATAL / Exception", srcLabel!);
                break;

            case "warn" or "warning":
                BuildFilteredLines(items, allLines, "WARN", "WARN / WARNING", srcLabel!);
                break;

            case "info":
                BuildFilteredLines(items, allLines, "INFO", "INFO / information", srcLabel!);
                break;

            case "debug" or "trace":
                BuildFilteredLines(items, allLines, "DEBUG", "DEBUG / TRACE", srcLabel!);
                break;

            case "last" or "tail":
            {
                int n = 20;
                if (parts.Length >= 2 && int.TryParse(parts[1], out var pn)) n = Math.Clamp(pn, 1, 500);
                var tail   = allLines.TakeLast(n).ToList();
                var joined = string.Join("\n", tail.Select(l => l.Raw));
                items.Add(new LauncherItem($"마지막 {tail.Count}줄 ({srcLabel})",
                    "Enter 전체 복사", null, ("copy", joined), Symbol: "\uE9D9"));
                foreach (var l in tail)
                    items.Add(BuildLineItem(l));
                break;
            }

            case "head" or "first":
            {
                int n = 20;
                if (parts.Length >= 2 && int.TryParse(parts[1], out var pn)) n = Math.Clamp(pn, 1, 500);
                var head   = allLines.Take(n).ToList();
                var joined = string.Join("\n", head.Select(l => l.Raw));
                items.Add(new LauncherItem($"처음 {head.Count}줄 ({srcLabel})",
                    "Enter 전체 복사", null, ("copy", joined), Symbol: "\uE9D9"));
                foreach (var l in head)
                    items.Add(BuildLineItem(l));
                break;
            }

            case "grep" or "find" or "search":
            {
                if (parts.Length < 2) { items.Add(ErrorItem("예: log grep Exception")); break; }
                var kw      = string.Join(" ", parts[1..]);
                var matched = allLines.Where(l => l.Raw.Contains(kw, StringComparison.OrdinalIgnoreCase)).ToList();
                var joined  = string.Join("\n", matched.Select(l => l.Raw));
                items.Add(new LauncherItem($"'{kw}' 검색 결과: {matched.Count}줄",
                    "Enter 전체 복사", null, ("copy", joined), Symbol: "\uE9D9"));
                foreach (var l in matched.Take(15))
                    items.Add(BuildLineItem(l));
                if (matched.Count > 15)
                    items.Add(new LauncherItem($"... ({matched.Count - 15}줄 더)", "Enter 전체 복사",
                        null, ("copy", joined), Symbol: "\uE9D9"));
                break;
            }

            case "stats":
                BuildStats(items, allLines, logSrc, srcLabel!);
                break;

            case "exceptions" or "exception" or "ex":
            {
                var blocks = ExtractExceptions(logSrc);
                items.Add(new LauncherItem($"예외 블록 {blocks.Count}개 발견",
                    srcLabel!, null, null, Symbol: "\uE9D9"));
                for (int i = 0; i < blocks.Count; i++)
                {
                    var b = blocks[i];
                    var preview = b.Split('\n')[0];
                    items.Add(new LauncherItem($"#{i + 1}  {TruncateStr(preview, 60)}",
                        $"{b.Split('\n').Length}줄", null, ("copy", b), Symbol: "\uE9D9"));
                }
                if (blocks.Count == 0)
                    items.Add(new LauncherItem("예외·스택트레이스 패턴 없음", "", null, null, Symbol: "\uE9D9"));
                break;
            }

            case "today":
            {
                var today   = DateTime.Today.ToString("yyyy-MM-dd");
                var today2  = DateTime.Today.ToString("yyyy/MM/dd");
                var matched = allLines.Where(l =>
                    l.Raw.Contains(today) || l.Raw.Contains(today2)).ToList();
                var joined  = string.Join("\n", matched.Select(l => l.Raw));
                items.Add(new LauncherItem($"오늘({today}) {matched.Count}줄",
                    "Enter 전체 복사", null, ("copy", joined), Symbol: "\uE9D9"));
                foreach (var l in matched.Take(15))
                    items.Add(BuildLineItem(l));
                break;
            }

            default:
                // 기본 요약으로 폴백
                BuildSummary(items, logSrc, srcLabel!);
                break;
        }

        return Task.FromResult<IEnumerable<LauncherItem>>(items);
    }

    public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
    {
        if (item.Data is ("copy", string text))
        {
            try
            {
                System.Windows.Application.Current.Dispatcher.Invoke(
                    () => Clipboard.SetText(text));
                NotificationService.Notify("Log", "클립보드에 복사했습니다.");
            }
            catch { }
        }
        return Task.CompletedTask;
    }

    // ── 빌더 ────────────────────────────────────────────────────────────────

    private static void BuildSummary(List<LauncherItem> items, string src, string label)
    {
        var lines    = ParseLogLines(src);
        var errors   = lines.Count(l => l.Level == "ERROR");
        var warns    = lines.Count(l => l.Level == "WARN");
        var infos    = lines.Count(l => l.Level == "INFO");
        var debugs   = lines.Count(l => l.Level == "DEBUG");
        var unknowns = lines.Count(l => l.Level == "OTHER");

        items.Add(new LauncherItem($"{label}  —  {lines.Count}줄",
            $"ERROR:{errors}  WARN:{warns}  INFO:{infos}  DEBUG:{debugs}",
            null, null, Symbol: "\uE9D9"));
        if (errors > 0)
        {
            items.Add(new LauncherItem($"🔴 ERROR  {errors}줄",
                "log error 서브커맨드로 상세 보기", null, null, Symbol: "\uE9D9"));
        }
        if (warns > 0)
        {
            items.Add(new LauncherItem($"🟡 WARN   {warns}줄",
                "log warn 서브커맨드로 상세 보기", null, null, Symbol: "\uE9D9"));
        }
        items.Add(CopyItem("전체 줄 수",  lines.Count.ToString()));
        items.Add(CopyItem("ERROR 줄",    errors.ToString()));
        items.Add(CopyItem("WARN 줄",     warns.ToString()));

        // 마지막 5줄 미리보기
        items.Add(new LauncherItem("── 마지막 5줄 ──", "", null, null, Symbol: "\uE9D9"));
        foreach (var l in lines.TakeLast(5))
            items.Add(BuildLineItem(l));
    }

    private static void BuildFilteredLines(List<LauncherItem> items, List<LogLine> lines,
        string level, string label, string srcLabel)
    {
        var filtered = lines.Where(l => l.Level == level).ToList();
        var joined   = string.Join("\n", filtered.Select(l => l.Raw));
        items.Add(new LauncherItem($"{label}  {filtered.Count}줄 ({srcLabel})",
            "Enter 전체 복사", null, ("copy", joined), Symbol: "\uE9D9"));
        if (filtered.Count == 0)
        {
            items.Add(new LauncherItem($"{label} 줄이 없습니다", "", null, null, Symbol: "\uE9D9"));
            return;
        }
        foreach (var l in filtered.Take(15))
            items.Add(BuildLineItem(l));
        if (filtered.Count > 15)
            items.Add(new LauncherItem($"... ({filtered.Count - 15}줄 더)", "Enter 전체 복사",
                null, ("copy", joined), Symbol: "\uE9D9"));
    }

    private static void BuildStats(List<LauncherItem> items, List<LogLine> lines, string src, string label)
    {
        var counts = new Dictionary<string, int>
        {
            ["ERROR"] = lines.Count(l => l.Level == "ERROR"),
            ["WARN"]  = lines.Count(l => l.Level == "WARN"),
            ["INFO"]  = lines.Count(l => l.Level == "INFO"),
            ["DEBUG"] = lines.Count(l => l.Level == "DEBUG"),
            ["OTHER"] = lines.Count(l => l.Level == "OTHER"),
        };

        items.Add(new LauncherItem($"로그 통계 ({label})", $"총 {lines.Count}줄",
            null, null, Symbol: "\uE9D9"));
        foreach (var (lvl, cnt) in counts.Where(kv => kv.Value > 0))
            items.Add(CopyItem(lvl, $"{cnt}줄  ({cnt * 100.0 / Math.Max(1, lines.Count):F1}%)"));

        // 날짜별 분포 (yyyy-MM-dd 패턴 추출)
        var dateCounts = new Dictionary<string, int>();
        foreach (var l in lines)
        {
            var m = DatePattern().Match(l.Raw);
            if (m.Success)
            {
                var date = m.Value[..10];
                dateCounts[date] = dateCounts.GetValueOrDefault(date) + 1;
            }
        }
        if (dateCounts.Count > 0)
        {
            items.Add(new LauncherItem("── 날짜별 분포 ──", "", null, null, Symbol: "\uE9D9"));
            foreach (var (date, cnt) in dateCounts.OrderByDescending(kv => kv.Key).Take(5))
                items.Add(CopyItem(date, $"{cnt}줄"));
        }
    }

    private static List<string> ExtractExceptions(string src)
    {
        var blocks  = new List<string>();
        var lines   = src.Split('\n');
        var inBlock = false;
        var current = new StringBuilder();

        foreach (var line in lines)
        {
            var isEx = line.Contains("Exception") || line.Contains("Error:") ||
                       line.Contains("at ") || line.TrimStart().StartsWith("Caused by");
            if (!inBlock && isEx)
            {
                inBlock = true;
                current.Clear();
                current.AppendLine(line);
            }
            else if (inBlock)
            {
                if (isEx || line.TrimStart().StartsWith("at ") || line.TrimStart().StartsWith("..."))
                    current.AppendLine(line);
                else
                {
                    if (current.Length > 0) blocks.Add(current.ToString().Trim());
                    inBlock = false;
                    current.Clear();
                }
            }
        }
        if (inBlock && current.Length > 0) blocks.Add(current.ToString().Trim());
        return blocks;
    }

    // ── 파서·헬퍼 ───────────────────────────────────────────────────────────

    private static List<LogLine> ParseLogLines(string src)
    {
        var lines = src.Split('\n');
        return lines.Select((raw, i) =>
        {
            var level = DetectLevel(raw);
            return new LogLine(i + 1, level, raw.TrimEnd('\r'));
        }).ToList();
    }

    private static string DetectLevel(string line)
    {
        foreach (var (level, keywords) in LevelPatterns)
            if (keywords.Any(kw => line.Contains(kw, StringComparison.Ordinal)))
                return level;
        return "OTHER";
    }

    private static LauncherItem BuildLineItem(LogLine l)
    {
        var icon = l.Level switch
        {
            "ERROR" => "🔴",
            "WARN"  => "🟡",
            "INFO"  => "🔵",
            "DEBUG" => "⚪",
            _       => "  "
        };
        var display = TruncateStr(l.Raw, 80);
        return new LauncherItem($"{icon} {display}",
            $"줄 {l.No}  ({l.Level})", null, ("copy", l.Raw), Symbol: "\uE9D9");
    }

    private static string TruncateStr(string s, int max) =>
        s.Length <= max ? s : s[..max] + "…";

    private static LauncherItem CopyItem(string label, string value) =>
        new(label, value, null, ("copy", value), Symbol: "\uE9D9");

    private static LauncherItem ErrorItem(string msg) =>
        new(msg, "올바른 입력 형식을 확인하세요", null, null, Symbol: "\uE783");

    [GeneratedRegex(@"\d{4}[-/]\d{2}[-/]\d{2}")]
    private static partial Regex DatePattern();
}
