from __future__ import annotations

import argparse
import fnmatch
import logging
import subprocess
import sys
from dataclasses import dataclass

logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger(__name__)

_cached_process_list: list[ProcessInfo] = []
_processes_cached: bool = False
_max_match_threshold: int = 10  # Maximum number of processes to match without confirmation


@dataclass
class ProcessInfo:
    """Process information class."""

    name: str
    pid: str


def get_matched_process(process_name: str) -> list[ProcessInfo]:
    """Get the list of matching processes.

    Returns:
        list[ProcessInfo]: List of matched processes
    """
    return [p for p in _cached_process_list if fnmatch.fnmatch(p.name.lower(), process_name.lower())]


def get_process_list(force_refresh: bool = False) -> None:
    """Get the list of processes.

    Args:
        force_refresh: Force refresh the process list even if cached.
    """
    global _processes_cached
    if _processes_cached and not force_refresh:
        return

    _cached_process_list.clear()
    if sys.platform == "win32":
        _get_process_list_windows()
    else:
        _get_process_list_unix()
    _processes_cached = True


def _get_process_list_windows(encoding: str = "gbk") -> None:
    try:
        result = subprocess.run(
            ["tasklist", "/fo", "csv", "/nh"],
            capture_output=True,
            text=True,
            encoding=encoding,
            check=True,
            timeout=10,  # Add timeout to prevent infinite wait
        )
        logger.debug(f"Decoded using {encoding}")
        for line in result.stdout.strip().split("\n"):
            if line:
                parts = line.split('","')
                if len(parts) >= 2:
                    name = parts[0].strip('"')
                    pid = parts[1].strip('"')
                    _cached_process_list.append(ProcessInfo(name, pid))
    except UnicodeDecodeError:
        logger.warning(f"Failed to decode using {encoding} encoding, trying other encoding")
        if encoding == "utf8":
            logger.error("All encoding attempts failed, unable to get process list")
            return
        # If GBK encoding fails, try UTF-8
        try:
            _get_process_list_windows(encoding="utf8")
            logger.debug("Decoded using utf8")
        except (
            subprocess.SubprocessError,
            OSError,
            ValueError,
            UnicodeDecodeError,
        ):
            logger.error("Failed to get process list")
            return
    except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
        logger.error(f"Failed to execute tasklist command: {e.__class__.__name__}")
        return
    except Exception as e:
        logger.error(f"Unknown error occurred while getting process list: {e.__class__.__name__}")
        return


def _get_process_list_unix() -> None:
    try:
        result = subprocess.run(
            ["ps", "-eo", "pid,comm", "--no-headers"],
            capture_output=True,
            text=True,
            check=True,
        )
        for line in result.stdout.strip().split("\n"):
            if line:
                parts = line.strip().split(maxsplit=1)
                if len(parts) >= 2:
                    pid = parts[0]
                    name = parts[1]
                    _cached_process_list.append(ProcessInfo(name, pid))
    except (subprocess.SubprocessError, OSError, ValueError):
        logger.error("Failed to get process list")
        return


def kill_process(process_name: str, force_refresh: bool = True) -> None:
    """Terminate process.

    Args:
        process_name: Process name
        force_refresh: Force refresh process list before killing (default True)
    """
    get_process_list(force_refresh=force_refresh)
    matched_processes = get_matched_process(process_name)

    if not matched_processes:
        logger.warning(f"Process `{process_name}` not found")
        return

    match_count = len(matched_processes)

    if match_count > _max_match_threshold:
        logger.warning(
            f"Found {match_count} processes matching '{process_name}' (exceeds threshold of {_max_match_threshold})",
        )
        logger.warning("Process names:")
        for i, p in enumerate(matched_processes, 1):
            logger.warning(f"  {i}. {p.name} (PID: {p.pid})")
        logger.warning("Use more specific process name to avoid accidental termination")
        return

    logger.info(
        f"Found {match_count} process(es) matching '{process_name}': {[m.name for m in matched_processes]}",
    )
    try:
        success_count = 0
        for process in matched_processes:
            if _kill_process_by_pid(process.pid):
                logger.info(f"Successfully terminated process {process.name} (PID: {process.pid})")
                success_count += 1
            else:
                logger.info(f"Failed to terminate process {process.name} (PID: {process.pid})")
    except (subprocess.SubprocessError, OSError, ValueError):
        logger.error("Failed to terminate process")
        return
    else:
        logger.info(f"Successfully terminated {success_count} process(es) matching '{process_name}'")


def _kill_process_by_pid(pid: str) -> bool:
    if sys.platform == "win32":
        return _kill_process_by_pid_windows(pid)
    else:
        return _kill_process_by_pid_unix(pid)


def _kill_process_by_pid_windows(pid: str) -> bool:
    """Terminate process by PID on Windows.

    Returns:
        bool: Whether the process was successfully terminated.
    """
    try:
        subprocess.run(
            ["taskkill", "/F", "/PID", pid],
            capture_output=True,
            text=True,
            check=True,
        )
        return True
    except subprocess.CalledProcessError:
        logger.error(f"Failed to terminate process PID {pid}")
        return False


def _kill_process_by_pid_unix(pid: str) -> bool:
    """Terminate process by PID on Unix/Linux.

    Returns:
        bool: Whether the process was successfully terminated.
    """
    try:
        subprocess.run(
            ["kill", "-9", pid],
            capture_output=True,
            text=True,
            check=True,
        )
        return True
    except subprocess.CalledProcessError:
        logger.error(f"Failed to terminate process PID {pid}")
        return False


def main() -> None:
    """Terminate process."""
    parser = argparse.ArgumentParser()
    parser.add_argument("processes", type=str, nargs="+", help="Process to terminate.")
    parser.add_argument("--debug", "-d", action="store_true", help="Enable debug mode.")
    parser.add_argument(
        "--force",
        "-f",
        action="store_true",
        help="Force termination even if match count exceeds threshold.",
    )

    args = parser.parse_args()

    if args.debug:
        logger.setLevel(logging.DEBUG)

    # Temporarily increase threshold if force flag is set
    global _max_match_threshold
    original_threshold = _max_match_threshold
    if args.force:
        _max_match_threshold = 1000  # Large number to effectively disable the limit

    # Fetch process list once for the first process, then reuse for remaining
    for i, process_name in enumerate(args.processes):
        force_refresh = i == 0
        kill_process(process_name, force_refresh=force_refresh)

    # Restore original threshold
    _max_match_threshold = original_threshold

    return
