using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Windows;
using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Themes;

namespace AxCopilot.Handlers;

/// <summary>
/// L10-3: SSL/TLS 인증서 체커 핸들러. "cert" 프리픽스로 사용합니다.
///
/// 예: cert google.com          → google.com의 인증서 정보 조회
///     cert github.com 443      → 포트 지정
///     cert https://example.com → URL 형식도 지원
/// Enter → 결과를 클립보드에 복사.
///
/// ⚠ 외부 인터넷 접속 필요. 사내 모드에서는 내부 호스트만 조회 가능.
/// </summary>
public class CertHandler : IActionHandler
{
    public string? Prefix => "cert";

    public PluginMetadata Metadata => new(
        "Cert",
        "SSL/TLS 인증서 체커 — 만료일 · 발급자 · SANs",
        "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(
                "SSL 인증서 체커",
                "예: cert google.com  /  cert 192.168.1.1  /  cert example.com 8443",
                null, null, Symbol: "\uE72E"));
            items.Add(new LauncherItem("cert google.com",    "google.com 인증서 조회", null, null, Symbol: "\uE72E"));
            items.Add(new LauncherItem("cert github.com",    "github.com 인증서 조회", null, null, Symbol: "\uE72E"));
            items.Add(new LauncherItem("cert 192.168.1.1",   "내부 서버 인증서 조회",  null, null, Symbol: "\uE72E"));
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        // 비동기 조회 시작 — 빠른 반환 후 결과를 기다리지 않음
        // 실제 조회는 ExecuteAsync에서 처리하며, 여기서는 "조회 중" 항목만 반환
        var (host, port) = ParseHostPort(q);

        if (string.IsNullOrWhiteSpace(host))
        {
            items.Add(new LauncherItem("형식 오류", "예: cert domain.com  또는  cert domain.com 443", null, null, Symbol: "\uE783"));
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        // 사내 모드 확인
        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}:{port} 인증서 조회",
            "Enter를 눌러 조회하세요",
            null,
            ("check", $"{host}:{port}"),
            Symbol: "\uE72E"));

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

        if (item.Data is not ("check", string target)) return;

        var parts = target.Split(':');
        var host  = parts[0];
        var port  = parts.Length > 1 && int.TryParse(parts[1], out var p) ? p : 443;

        NotificationService.Notify("Cert", $"{host}:{port} 인증서 조회 중…");

        try
        {
            var certInfo = await FetchCertInfoAsync(host, port, ct);
            var summary  = BuildSummary(certInfo);
            System.Windows.Application.Current.Dispatcher.Invoke(
                () => Clipboard.SetText(summary));
            NotificationService.Notify("Cert", certInfo.StatusLine);
        }
        catch (OperationCanceledException)
        {
            NotificationService.Notify("Cert", "조회가 취소되었습니다.");
        }
        catch (Exception ex)
        {
            NotificationService.Notify("Cert", $"오류: {ex.Message}");
        }
    }

    // ── 인증서 조회 ──────────────────────────────────────────────────────────

    private static async Task<CertInfo> FetchCertInfoAsync(string host, int port, CancellationToken ct)
    {
        using var client = new TcpClient();
        await client.ConnectAsync(host, port, ct);

        using var sslStream = new SslStream(
            client.GetStream(),
            leaveInnerStreamOpen: false,
            userCertificateValidationCallback: (_, cert, _, _) => true); // 만료 인증서도 정보 확인

        await sslStream.AuthenticateAsClientAsync(
            new SslClientAuthenticationOptions
            {
                TargetHost = host,
                RemoteCertificateValidationCallback = (_, _, _, _) => true,
            }, ct);

        var cert = sslStream.RemoteCertificate as X509Certificate2
                ?? new X509Certificate2(sslStream.RemoteCertificate!);

        return BuildCertInfo(host, port, cert);
    }

    private static CertInfo BuildCertInfo(string host, int port, X509Certificate2 cert)
    {
        var now      = DateTime.UtcNow;
        var notAfter = cert.NotAfter.ToUniversalTime();
        var daysLeft = (int)(notAfter - now).TotalDays;
        var subject  = cert.Subject;
        var issuer   = cert.Issuer;

        // SANs (Subject Alternative Names)
        var sans = new List<string>();
        foreach (var ext in cert.Extensions)
        {
            if (ext.Oid?.Value == "2.5.29.17") // SAN OID
            {
                var raw = ext.Format(false);
                sans.AddRange(raw.Split(new[] { ", ", ",\r\n" }, StringSplitOptions.RemoveEmptyEntries)
                                  .Select(s => s.Trim())
                                  .Where(s => s.StartsWith("DNS Name=", StringComparison.OrdinalIgnoreCase))
                                  .Select(s => s[9..]));
            }
        }

        var status = daysLeft > 30 ? "유효"
                   : daysLeft > 0  ? "만료 임박"
                   : "만료됨";

        return new CertInfo
        {
            Host       = host,
            Port       = port,
            Subject    = subject,
            Issuer     = issuer,
            NotBefore  = cert.NotBefore,
            NotAfter   = cert.NotAfter,
            DaysLeft   = daysLeft,
            Sans       = sans,
            Thumbprint = cert.Thumbprint,
            Status     = status,
            StatusLine = $"{status}  · D-{(daysLeft > 0 ? daysLeft.ToString() : "만료")}  · {host}:{port}",
        };
    }

    private static string BuildSummary(CertInfo c)
    {
        var sb = new System.Text.StringBuilder();
        sb.AppendLine($"호스트:     {c.Host}:{c.Port}");
        sb.AppendLine($"상태:       {c.Status}  (만료까지 {c.DaysLeft}일)");
        sb.AppendLine($"발급 대상:  {c.Subject}");
        sb.AppendLine($"발급 기관:  {c.Issuer}");
        sb.AppendLine($"유효 시작:  {c.NotBefore:yyyy-MM-dd HH:mm:ss}");
        sb.AppendLine($"만료 일자:  {c.NotAfter:yyyy-MM-dd HH:mm:ss}");
        if (c.Sans.Count > 0)
            sb.AppendLine($"SANs:       {string.Join(", ", c.Sans.Take(10))}");
        sb.AppendLine($"지문(SHA1): {c.Thumbprint}");
        return sb.ToString().TrimEnd();
    }

    // ── 파싱 헬퍼 ────────────────────────────────────────────────────────────

    private static (string Host, int Port) ParseHostPort(string q)
    {
        // https:// 또는 http:// 제거
        if (q.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
            q = q[8..];
        else if (q.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
            q = q[7..];

        // 경로 제거
        var slashIdx = q.IndexOf('/');
        if (slashIdx >= 0) q = q[..slashIdx];

        var colonIdx = q.LastIndexOf(':');
        if (colonIdx >= 0 && int.TryParse(q[(colonIdx + 1)..], out var port))
            return (q[..colonIdx], port);

        return (q, 443);
    }

    private static bool IsInternalHost(string host)
    {
        if (host is "localhost" or "127.0.0.1") return true;
        if (host.StartsWith("192.168.")) return true;
        if (host.StartsWith("10."))       return true;
        if (host.StartsWith("172.16.") || host.StartsWith("172.17.") ||
            host.StartsWith("172.18.") || host.StartsWith("172.19.") ||
            host.StartsWith("172.20.") || host.StartsWith("172.21.") ||
            host.StartsWith("172.22.") || host.StartsWith("172.23.") ||
            host.StartsWith("172.24.") || host.StartsWith("172.25.") ||
            host.StartsWith("172.26.") || host.StartsWith("172.27.") ||
            host.StartsWith("172.28.") || host.StartsWith("172.29.") ||
            host.StartsWith("172.30.") || host.StartsWith("172.31.")) return true;
        return false;
    }

    private record CertInfo
    {
        public string Host       { get; init; } = "";
        public int    Port       { get; init; }
        public string Subject    { get; init; } = "";
        public string Issuer     { get; init; } = "";
        public DateTime NotBefore { get; init; }
        public DateTime NotAfter  { get; init; }
        public int    DaysLeft   { get; init; }
        public List<string> Sans { get; init; } = new();
        public string Thumbprint { get; init; } = "";
        public string Status     { get; init; } = "";
        public string StatusLine { get; init; } = "";
    }
}
