"""Port and process detection for console projects."""

import socket
import subprocess
import sys
from dataclasses import dataclass


@dataclass
class PortStatus:
    """Status information for a port."""

    port: int
    is_listening: bool
    process_name: str | None = None
    pid: int | None = None


def check_port(port: int, host: str = "127.0.0.1") -> bool:
    """Check if a port is listening using a socket connection.

    Args:
        port: Port number to check
        host: Host to check (default: localhost)

    Returns:
        True if port is listening, False otherwise
    """
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(1)
    try:
        result = sock.connect_ex((host, port))
        return result == 0
    except (OSError, socket.error):
        return False
    finally:
        sock.close()


def get_process_on_port(port: int) -> tuple[str | None, int | None]:
    """Get the process name and PID listening on a port.

    Uses lsof on macOS/Linux, netstat on Windows.

    Args:
        port: Port number to check

    Returns:
        Tuple of (process_name, pid) or (None, None) if not found
    """
    if sys.platform == "win32":
        return _get_process_windows(port)
    else:
        return _get_process_unix(port)


def _get_process_unix(port: int) -> tuple[str | None, int | None]:
    """Get process info using lsof (macOS/Linux)."""
    try:
        result = subprocess.run(
            ["lsof", "-i", f":{port}", "-sTCP:LISTEN", "-t", "-P"],
            capture_output=True,
            text=True,
            timeout=5,
        )

        if result.returncode != 0 or not result.stdout.strip():
            return None, None

        # Get the first PID
        pid = int(result.stdout.strip().split("\n")[0])

        # Get the process name
        ps_result = subprocess.run(
            ["ps", "-p", str(pid), "-o", "comm="],
            capture_output=True,
            text=True,
            timeout=5,
        )

        if ps_result.returncode == 0 and ps_result.stdout.strip():
            process_name = ps_result.stdout.strip()
            return process_name, pid

        return None, pid

    except (subprocess.TimeoutExpired, subprocess.SubprocessError, ValueError):
        return None, None


def _get_process_windows(port: int) -> tuple[str | None, int | None]:
    """Get process info using netstat (Windows)."""
    try:
        result = subprocess.run(
            ["netstat", "-ano", "-p", "TCP"],
            capture_output=True,
            text=True,
            timeout=10,
        )

        if result.returncode != 0:
            return None, None

        for line in result.stdout.split("\n"):
            if f":{port}" in line and "LISTENING" in line:
                parts = line.split()
                if len(parts) >= 5:
                    pid = int(parts[-1])

                    # Get process name using tasklist
                    tasklist_result = subprocess.run(
                        ["tasklist", "/FI", f"PID eq {pid}", "/FO", "CSV", "/NH"],
                        capture_output=True,
                        text=True,
                        timeout=5,
                    )

                    if tasklist_result.returncode == 0 and tasklist_result.stdout.strip():
                        # Parse CSV: "process.exe","pid","..."
                        csv_line = tasklist_result.stdout.strip().split("\n")[0]
                        if csv_line.startswith('"'):
                            process_name = csv_line.split(",")[0].strip('"')
                            return process_name, pid

                    return None, pid

        return None, None

    except (subprocess.TimeoutExpired, subprocess.SubprocessError, ValueError):
        return None, None


def get_port_status(port: int) -> PortStatus:
    """Get complete status for a port.

    Args:
        port: Port number to check

    Returns:
        PortStatus with listening status and process info
    """
    is_listening = check_port(port)

    if is_listening:
        process_name, pid = get_process_on_port(port)
        return PortStatus(
            port=port,
            is_listening=True,
            process_name=process_name,
            pid=pid,
        )

    return PortStatus(port=port, is_listening=False)


def scan_ports(ports: list[int]) -> dict[int, PortStatus]:
    """Scan multiple ports and return their status.

    Args:
        ports: List of port numbers to check

    Returns:
        Dict mapping port -> PortStatus
    """
    return {port: get_port_status(port) for port in ports}
