using System.Diagnostics;
using System.Net.NetworkInformation;
using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Themes;

namespace AxCopilot.Handlers;

/// <summary>
/// 포트/프로세스 점검 핸들러. "port " 프리픽스로 사용합니다.
/// 예: port           → 활성 TCP 연결 목록
///     port 8080      → 8080 포트를 점유 중인 프로세스 상세
///     port chrome    → chrome이 사용하는 포트 목록
/// </summary>
public class PortHandler : IActionHandler
{
    public string? Prefix => "port";

    public PluginMetadata Metadata => new(
        "PortChecker",
        "포트 & 프로세스 점검 — port 뒤에 포트번호 또는 프로세스명",
        "1.0",
        "AX");

    // 프로세스 이름 캐시 (PID → 이름), 5초 유효
    private static readonly Dictionary<int, string> _procCache = new();
    // netstat 결과 캐시 (포트 → PID), 5초 유효 — netstat 단일 실행으로 N+1 해결
    private static readonly Dictionary<int, int> _pidMap = new();
    private static DateTime _cacheExpiry = DateTime.MinValue;

    public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
    {
        RefreshProcessCache();

        TcpConnectionInformation[] tcpConns;

        try
        {
            var props = IPGlobalProperties.GetIPGlobalProperties();
            tcpConns = props.GetActiveTcpConnections();
        }
        catch (Exception ex)
        {
            LogService.Warn($"포트 조회 실패: {ex.Message}");
            return Task.FromResult<IEnumerable<LauncherItem>>(
            [
                new LauncherItem("네트워크 정보를 가져올 수 없습니다", ex.Message, null, null, Symbol: Symbols.Warning)
            ]);
        }

        var q = query.Trim();

        // ─── 빈 쿼리: 활성 연결 상위 목록 ────────────────────────────────────
        if (string.IsNullOrWhiteSpace(q))
        {
            var items = tcpConns
                .Where(c => c.State == TcpState.Established || c.State == TcpState.Listen)
                .OrderBy(c => c.LocalEndPoint.Port)
                .Take(20)
                .Select(c =>
                {
                    var procName = GetProcessNameForPort(c.LocalEndPoint.Port);
                    var state = c.State == TcpState.Listen ? "LISTEN" : "ESTABLISHED";
                    return new LauncherItem(
                        $":{c.LocalEndPoint.Port}  →  {c.RemoteEndPoint}",
                        $"{state}  ·  {procName}  ·  Enter로 포트번호 복사",
                        null,
                        c.LocalEndPoint.Port.ToString(),
                        Symbol: Symbols.Network);
                })
                .ToList<LauncherItem>();

            if (!items.Any())
                items.Add(new LauncherItem("활성 연결 없음", "TCP 연결이 감지되지 않았습니다", null, null, Symbol: Symbols.Network));

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

        // ─── 숫자: 포트 번호 검색 ─────────────────────────────────────────────
        if (int.TryParse(q, out var portNum))
        {
            var matches = tcpConns
                .Where(c => c.LocalEndPoint.Port == portNum || c.RemoteEndPoint.Port == portNum)
                .ToList();

            if (!matches.Any())
            {
                return Task.FromResult<IEnumerable<LauncherItem>>(
                [
                    new LauncherItem(
                        $"포트 {portNum} — 사용 중 아님",
                        "해당 포트를 사용하는 TCP 연결이 없습니다",
                        null,
                        portNum.ToString(),
                        Symbol: Symbols.Info)
                ]);
            }

            var result = matches.Select(c =>
            {
                var procName = GetProcessNameForPort(c.LocalEndPoint.Port);
                var pid = GetPidForPort(c.LocalEndPoint.Port);
                return new LauncherItem(
                    $":{c.LocalEndPoint.Port}  ←→  {c.RemoteEndPoint}",
                    $"{c.State}  ·  {procName} (PID {pid})  ·  Enter로 PID 복사",
                    null,
                    pid > 0 ? pid.ToString() : portNum.ToString(),
                    Symbol: Symbols.Network);
            }).ToList<LauncherItem>();

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

        // ─── 문자열: 프로세스명 검색 ──────────────────────────────────────────
        var procLower = q.ToLowerInvariant();
        var procPorts = tcpConns
            .Where(c =>
            {
                var name = GetProcessNameForPort(c.LocalEndPoint.Port).ToLowerInvariant();
                return name.Contains(procLower);
            })
            .Take(15)
            .Select(c =>
            {
                var procName = GetProcessNameForPort(c.LocalEndPoint.Port);
                return new LauncherItem(
                    $"{procName}  :  {c.LocalEndPoint.Port}  →  {c.RemoteEndPoint}",
                    $"{c.State}  ·  Enter로 포트번호 복사",
                    null,
                    c.LocalEndPoint.Port.ToString(),
                    Symbol: Symbols.Network);
            })
            .ToList<LauncherItem>();

        if (!procPorts.Any())
            procPorts.Add(new LauncherItem(
                $"'{q}' — 연결 없음",
                "해당 프로세스의 TCP 연결이 없습니다",
                null, null, Symbol: Symbols.Warning));

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

    public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
    {
        if (item.Data is string text)
        {
            try { System.Windows.Clipboard.SetText(text); } catch (Exception) { }
        }
        return Task.CompletedTask;
    }

    // ─── 프로세스/PID 캐시 헬퍼 ──────────────────────────────────────────────────

    /// <summary>
    /// 프로세스 목록 + netstat PID 맵을 한 번에 갱신 (5초 캐시).
    /// GetItemsAsync 진입 시 한 번만 호출하여 N+1 netstat 실행 방지.
    /// </summary>
    private static void RefreshProcessCache()
    {
        if (DateTime.Now < _cacheExpiry) return;

        _procCache.Clear();
        _pidMap.Clear();

        // ① 프로세스 목록 (PID → 이름)
        try
        {
            foreach (var p in Process.GetProcesses())
            {
                try { _procCache[p.Id] = p.ProcessName; }
                catch (Exception) { }
            }
        }
        catch (Exception ex)
        {
            LogService.Warn($"프로세스 목록 갱신 실패: {ex.Message}");
        }

        // ② netstat -ano 단 1회 실행 → 포트→PID 전체 맵 구축
        try
        {
            var psi = new ProcessStartInfo("netstat", "-ano")
            {
                RedirectStandardOutput = true,
                UseShellExecute = false,
                CreateNoWindow = true
            };
            using var proc = Process.Start(psi);
            if (proc != null)
            {
                var output = proc.StandardOutput.ReadToEnd();
                proc.WaitForExit(2000);

                foreach (var line in output.Split('\n'))
                {
                    var parts = line.Trim().Split([' ', '\t'], StringSplitOptions.RemoveEmptyEntries);
                    // 형식: Proto  Local Address   Foreign Address  State  PID
                    if (parts.Length < 5) continue;
                    if (!int.TryParse(parts[^1], out var pid)) continue;

                    // Local Address에서 포트 추출 (예: 0.0.0.0:8080 또는 [::]:8080)
                    var localAddr = parts[1];
                    var colonIdx = localAddr.LastIndexOf(':');
                    if (colonIdx >= 0 && int.TryParse(localAddr[(colonIdx + 1)..], out var port))
                        _pidMap.TryAdd(port, pid);
                }
            }
        }
        catch (Exception ex)
        {
            LogService.Warn($"netstat 실행 실패: {ex.Message}");
        }

        _cacheExpiry = DateTime.Now.AddSeconds(5);
    }

    private static string GetProcessNameForPort(int port)
    {
        var pid = GetPidForPort(port);
        return pid > 0 && _procCache.TryGetValue(pid, out var name) ? name : "알 수 없음";
    }

    /// <summary>
    /// 캐시된 pidMap에서 즉시 반환 — netstat를 추가로 실행하지 않음.
    /// </summary>
    private static int GetPidForPort(int port)
        => _pidMap.TryGetValue(port, out var pid) ? pid : -1;
}
