using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows;
using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Themes;

namespace AxCopilot.Handlers;

/// <summary>
/// L13-1: DNS 레코드 조회 핸들러. "dns" 프리픽스로 사용합니다.
///
/// 예: dns google.com           → A/AAAA 레코드 조회
///     dns google.com mx        → MX 레코드
///     dns google.com txt       → TXT 레코드
///     dns google.com ns        → NS 레코드
///     dns 8.8.8.8              → PTR(역방향) 조회
/// Enter → 결과를 클립보드에 복사.
///
/// ⚠ 사내 모드: 내부 호스트만 조회 허용.
/// </summary>
public class DnsQueryHandler : IActionHandler
{
    public string? Prefix => "dns";

    public PluginMetadata Metadata => new(
        "DNS",
        "DNS 레코드 조회 — A · AAAA · MX · TXT · NS · PTR",
        "1.0",
        "AX");

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

        if (string.IsNullOrWhiteSpace(q))
        {
            items.Add(new LauncherItem("DNS 레코드 조회",
                "예: dns google.com  /  dns google.com mx  /  dns 8.8.8.8",
                null, null, Symbol: "\uE968"));
            items.Add(new LauncherItem("dns localhost",      "로컬 A 레코드",  null, null, Symbol: "\uE968"));
            items.Add(new LauncherItem("dns example.com mx", "MX 레코드",     null, null, Symbol: "\uE968"));
            items.Add(new LauncherItem("dns example.com txt","TXT 레코드",    null, null, Symbol: "\uE968"));
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        var parts    = q.Split(' ', StringSplitOptions.RemoveEmptyEntries);
        var host     = parts[0];
        var recType  = parts.Length > 1 ? parts[1].ToUpperInvariant() : "A";

        // 사내 모드 확인
        var settings   = (System.Windows.Application.Current as App)?.SettingsService?.Settings;
        var isInternal = settings?.InternalModeEnabled ?? true;
        if (isInternal && !IsInternalHost(host))
        {
            items.Add(new LauncherItem(
                "사내 모드 제한",
                $"'{host}'은 외부 호스트입니다. 설정에서 사외 모드를 활성화하세요.",
                null, null, Symbol: "\uE783"));
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        items.Add(new LauncherItem(
            $"{host}  [{recType}]",
            "Enter를 눌러 조회 실행",
            null,
            ("query", $"{host}|{recType}"),
            Symbol: "\uE968"));

        // 레코드 타입 빠른 선택 힌트
        if (parts.Length == 1)
        {
            foreach (var t in new[] { "A", "AAAA", "MX", "TXT", "NS" })
                items.Add(new LauncherItem($"dns {host} {t}", $"{t} 레코드 조회", null,
                    ("query", $"{host}|{t}"), Symbol: "\uE968"));
        }

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

    public async 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("DNS", "클립보드에 복사했습니다.");
            }
            catch { /* 비핵심 */ }
            return;
        }

        if (item.Data is not ("query", string queryData)) return;

        var idx      = queryData.IndexOf('|');
        var host     = queryData[..idx];
        var recType  = queryData[(idx + 1)..];

        NotificationService.Notify("DNS", $"{host} [{recType}] 조회 중…");

        try
        {
            var results = await QueryDnsAsync(host, recType, ct);
            if (results.Count == 0)
            {
                NotificationService.Notify("DNS", $"{host} [{recType}] — 레코드 없음");
                return;
            }

            var summary = string.Join("\n", results);
            System.Windows.Application.Current.Dispatcher.Invoke(() => Clipboard.SetText(summary));
            NotificationService.Notify("DNS", $"{results.Count}개 레코드 조회됨  · 클립보드 복사");
        }
        catch (OperationCanceledException)
        {
            NotificationService.Notify("DNS", "조회 취소됨");
        }
        catch (Exception ex)
        {
            NotificationService.Notify("DNS", $"오류: {ex.Message}");
        }
    }

    // ── DNS 조회 ─────────────────────────────────────────────────────────────

    private static async Task<List<string>> QueryDnsAsync(string host, string type, CancellationToken ct)
    {
        return type switch
        {
            "A" or "AAAA" => await QueryAAsync(host, type, ct),
            "PTR"         => await QueryPtrAsync(host, ct),
            _             => await QueryViaNslookupAsync(host, type, ct),
        };
    }

    private static async Task<List<string>> QueryAAsync(string host, string type, CancellationToken ct)
    {
        var family = type == "AAAA" ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork;
        var addrs  = await Dns.GetHostAddressesAsync(host, family, ct);
        return addrs.Select(a => a.ToString()).ToList();
    }

    private static async Task<List<string>> QueryPtrAsync(string ip, CancellationToken ct)
    {
        if (!IPAddress.TryParse(ip, out _))
            return [$"'{ip}'은 유효한 IP 주소가 아닙니다"];
        try
        {
            var entry = await Dns.GetHostEntryAsync(ip, ct);
            return [entry.HostName];
        }
        catch
        {
            return [$"PTR 레코드 없음: {ip}"];
        }
    }

    /// <summary>MX/TXT/NS/CNAME: nslookup 프로세스 실행으로 조회</summary>
    private static async Task<List<string>> QueryViaNslookupAsync(string host, string type, CancellationToken ct)
    {
        var psi = new System.Diagnostics.ProcessStartInfo
        {
            FileName               = "nslookup",
            Arguments              = $"-type={type} {host}",
            UseShellExecute        = false,
            RedirectStandardOutput = true,
            RedirectStandardError  = true,
            CreateNoWindow         = true,
            StandardOutputEncoding = Encoding.UTF8,
        };

        using var proc = new System.Diagnostics.Process { StartInfo = psi };
        proc.Start();

        var stdout = await proc.StandardOutput.ReadToEndAsync(ct);
        await proc.WaitForExitAsync(ct);

        return ParseNslookupOutput(stdout, type);
    }

    private static List<string> ParseNslookupOutput(string output, string type)
    {
        var results = new List<string>();
        var lines   = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);

        // 서버 응답 헤더 건너뜀 (첫 2줄)
        var skip    = true;
        foreach (var line in lines)
        {
            var trimmed = line.Trim();
            if (skip)
            {
                if (trimmed.StartsWith("Non-authoritative", StringComparison.OrdinalIgnoreCase) ||
                    trimmed.StartsWith("Name:", StringComparison.OrdinalIgnoreCase) ||
                    (type == "MX" && trimmed.Contains("mail exchanger")) ||
                    (type == "TXT" && trimmed.Contains("text =")) ||
                    (type == "NS" && trimmed.Contains("nameserver")))
                    skip = false;
                else
                    continue;
            }

            if (string.IsNullOrWhiteSpace(trimmed)) continue;

            // MX: "... mail exchanger = 10 aspmx.l.google.com"
            if (type == "MX" && trimmed.Contains("mail exchanger"))
            {
                var idx = trimmed.IndexOf('=');
                if (idx >= 0) results.Add(trimmed[(idx + 1)..].Trim());
                continue;
            }

            // TXT: "... text = "v=spf1 …""
            if (type == "TXT" && trimmed.Contains("text ="))
            {
                var idx = trimmed.IndexOf("text =", StringComparison.OrdinalIgnoreCase);
                if (idx >= 0) results.Add(trimmed[(idx + 6)..].Trim().Trim('"'));
                continue;
            }

            // NS: "nameserver = ns1.google.com"
            if (type == "NS" && trimmed.Contains("nameserver"))
            {
                var idx = trimmed.IndexOf('=');
                if (idx >= 0) results.Add(trimmed[(idx + 1)..].Trim());
                continue;
            }

            // CNAME: "canonical name = …"
            if (type == "CNAME" && trimmed.Contains("canonical name"))
            {
                var idx = trimmed.IndexOf('=');
                if (idx >= 0) results.Add(trimmed[(idx + 1)..].Trim());
                continue;
            }

            // Address: 주소 행
            if (trimmed.StartsWith("Address:", StringComparison.OrdinalIgnoreCase))
            {
                var idx = trimmed.IndexOf(':');
                if (idx >= 0) results.Add(trimmed[(idx + 1)..].Trim());
            }
        }

        return results.Count > 0 ? results : [$"조회 결과 없음 ({type})"];
    }

    private static bool IsInternalHost(string host)
    {
        if (host is "localhost" or "127.0.0.1") return true;
        if (IPAddress.TryParse(host, out var addr))
        {
            var s = addr.ToString();
            return s.StartsWith("192.168.") || s.StartsWith("10.") ||
                   System.Text.RegularExpressions.Regex.IsMatch(s,
                       @"^172\.(1[6-9]|2\d|3[01])\.");
        }
        // 도메인 이름은 사내 모드에서 외부로 간주
        return false;
    }
}
