using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Windows;
using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Themes;

namespace AxCopilot.Handlers;

/// <summary>
/// L8-1: 파일 해시 검증 핸들러. "hash" 프리픽스로 사용합니다.
///
/// 예: hash                       → 사용법 안내
///     hash C:\file.zip           → SHA256 (기본) 계산
///     hash md5 C:\file.zip       → MD5 계산
///     hash sha1 C:\file.zip      → SHA1 계산
///     hash sha512 C:\file.zip    → SHA512 계산
///     hash check <기대값>        → 클립보드의 해시값과 비교
/// 경로 미입력 시 클립보드에서 파일 경로 자동 감지.
/// Enter → 해시 결과를 클립보드에 복사.
/// </summary>
public class FileHashHandler : IActionHandler
{
    public string? Prefix => "hash";

    public PluginMetadata Metadata => new(
        "FileHash",
        "파일 해시 검증 — MD5 · SHA1 · SHA256 · SHA512",
        "1.0",
        "AX");

    private static readonly string[] Algos = ["md5", "sha1", "sha256", "sha512"];

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

        if (string.IsNullOrWhiteSpace(q))
        {
            // 클립보드에 파일 경로가 있으면 자동 감지
            var clipPath = GetClipboardFilePath();
            if (!string.IsNullOrEmpty(clipPath))
            {
                items.Add(new LauncherItem(
                    $"SHA256: {Path.GetFileName(clipPath)}",
                    clipPath,
                    null,
                    ("compute", "sha256", clipPath),
                    Symbol: "\uE8C4"));

                foreach (var algo in Algos)
                    items.Add(new LauncherItem(
                        $"hash {algo}",
                        $"{algo.ToUpperInvariant()} 계산",
                        null,
                        ("compute", algo, clipPath),
                        Symbol: "\uE8C4"));
            }
            else
            {
                items.Add(new LauncherItem(
                    "파일 해시 계산",
                    "hash <경로>  또는  hash md5|sha1|sha256|sha512 <경로>",
                    null, null, Symbol: "\uE8C4"));
                items.Add(new LauncherItem(
                    "hash check <기대 해시값>",
                    "클립보드의 해시와 비교 검증",
                    null, null, Symbol: "\uE73E"));
            }
            return items;
        }

        // "check <hashValue>" — 클립보드 해시 비교
        if (q.StartsWith("check ", StringComparison.OrdinalIgnoreCase))
        {
            var expected = q[6..].Trim();
            var clipText = GetClipboardText()?.Trim();
            if (!string.IsNullOrEmpty(clipText) && !string.IsNullOrEmpty(expected))
            {
                var match = expected.Equals(clipText, StringComparison.OrdinalIgnoreCase);
                items.Add(new LauncherItem(
                    match ? "✓ 해시 일치" : "✗ 해시 불일치",
                    $"기대값: {Truncate(expected, 40)}",
                    null, null,
                    Symbol: match ? "\uE73E" : "\uE711"));
                if (!match)
                    items.Add(new LauncherItem(
                        "클립보드",
                        Truncate(clipText, 60),
                        null, null, Symbol: "\uE8C8"));
            }
            else
            {
                items.Add(new LauncherItem(
                    "비교 대상 없음",
                    "먼저 해시 계산 결과를 클립보드에 복사하세요",
                    null, null, Symbol: "\uE783"));
            }
            return items;
        }

        // 알고리즘 + 경로 파싱
        string algo2    = "sha256";
        string filePath = q;

        var parts = q.Split(' ', 2);
        if (parts.Length == 2 && Algos.Contains(parts[0].ToLowerInvariant()))
        {
            algo2    = parts[0].ToLowerInvariant();
            filePath = parts[1].Trim().Trim('"');
        }
        else
        {
            // 알고리즘 없이 경로만 → 모든 알고리즘 표시
            filePath = q.Trim('"');
        }

        if (!File.Exists(filePath))
        {
            // 클립보드 경로 시도
            var clipPath = GetClipboardFilePath();
            if (!string.IsNullOrEmpty(clipPath) && File.Exists(clipPath))
                filePath = clipPath;
            else
            {
                items.Add(new LauncherItem(
                    "파일을 찾을 수 없음",
                    filePath,
                    null, null, Symbol: "\uE783"));
                return items;
            }
        }

        var fileName = Path.GetFileName(filePath);
        var fileSize = new FileInfo(filePath).Length;
        var sizeMb   = fileSize / 1024.0 / 1024.0;

        if (algo2 == "sha256" && parts.Length == 1)
        {
            // 경로만 입력 → 모든 알고리즘 항목 표시
            items.Add(new LauncherItem(
                fileName,
                $"{sizeMb:F1} MB",
                null, null, Symbol: "\uE8F4"));

            foreach (var a in Algos)
            {
                items.Add(new LauncherItem(
                    a.ToUpperInvariant(),
                    "계산 중... (Enter로 실행)",
                    null,
                    ("compute", a, filePath),
                    Symbol: "\uE8C4"));
            }
        }
        else
        {
            // 특정 알고리즘 계산
            items.Add(new LauncherItem(
                $"계산 중: {algo2.ToUpperInvariant()}",
                $"{fileName}  ({sizeMb:F1} MB)",
                null,
                ("compute", algo2, filePath),
                Symbol: "\uE8C4"));

            try
            {
                var hash = await ComputeHashAsync(filePath, algo2, ct);
                items.Clear();
                items.Add(new LauncherItem(
                    hash,
                    $"{algo2.ToUpperInvariant()}  ·  {fileName}",
                    null,
                    ("copy", hash),
                    Symbol: "\uE8C4"));
            }
            catch (OperationCanceledException) { }
            catch (Exception ex)
            {
                items.Add(new LauncherItem("해시 계산 실패", ex.Message, null, null, Symbol: "\uE783"));
            }
        }

        return items;
    }

    public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
    {
        switch (item.Data)
        {
            case ("copy", string hash):
                TryCopyToClipboard(hash);
                NotificationService.Notify("FileHash", "해시를 클립보드에 복사했습니다.");
                break;

            case ("compute", string algo, string filePath):
                try
                {
                    var hash = await ComputeHashAsync(filePath, algo, ct);
                    TryCopyToClipboard(hash);
                    NotificationService.Notify(
                        $"{algo.ToUpperInvariant()} 완료",
                        $"{Path.GetFileName(filePath)}: {Truncate(hash, 32)}…");
                }
                catch (Exception ex)
                {
                    NotificationService.Notify("FileHash 오류", ex.Message);
                }
                break;
        }
    }

    // ── 헬퍼 ────────────────────────────────────────────────────────────────

    private static async Task<string> ComputeHashAsync(
        string filePath, string algo, CancellationToken ct)
    {
        using HashAlgorithm hasher = algo.ToLowerInvariant() switch
        {
            "md5"    => MD5.Create(),
            "sha1"   => SHA1.Create(),
            "sha512" => SHA512.Create(),
            _        => SHA256.Create(),
        };

        await using var stream = File.OpenRead(filePath);
        var hashBytes = await Task.Run(() => hasher.ComputeHash(stream), ct);
        return Convert.ToHexString(hashBytes).ToLowerInvariant();
    }

    private static string? GetClipboardFilePath()
    {
        try
        {
            string? text = null;
            System.Windows.Application.Current.Dispatcher.Invoke(() =>
            {
                if (Clipboard.ContainsText())
                    text = Clipboard.GetText()?.Trim().Trim('"');
            });
            return !string.IsNullOrEmpty(text) && File.Exists(text) ? text : null;
        }
        catch { return null; }
    }

    private static string? GetClipboardText()
    {
        try
        {
            string? text = null;
            System.Windows.Application.Current.Dispatcher.Invoke(() =>
            {
                if (Clipboard.ContainsText()) text = Clipboard.GetText();
            });
            return text;
        }
        catch { return null; }
    }

    private static void TryCopyToClipboard(string text)
    {
        try
        {
            System.Windows.Application.Current.Dispatcher.Invoke(() => Clipboard.SetText(text));
        }
        catch { /* 비핵심 */ }
    }

    private static string Truncate(string s, int max) =>
        s.Length <= max ? s : s[..max];
}
