"""Find executable path for commands."""

from __future__ import annotations

import argparse
import logging
import os
import platform
import subprocess
import time
from dataclasses import dataclass

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

IS_WINDOWS = platform.system() == "Windows"
EXTENSION = ".exe" if IS_WINDOWS else ""
WINDOWS_CMD_PATH = "C:\\Windows\\System32\\cmd.exe"
WINDOWS_BUILTINS: set[str] = {
    "assoc",
    "attrib",
    "break",
    "bcdedit",
    "cacls",
    "call",
    "cd",
    "chcp",
    "chdir",
    "chkdsk",
    "chkntfs",
    "cls",
    "cmd",
    "color",
    "comp",
    "compact",
    "convert",
    "copy",
    "date",
    "del",
    "dir",
    "diskpart",
    "doskey",
    "driverquery",
    "echo",
    "endlocal",
    "erase",
    "exit",
    "fc",
    "find",
    "findstr",
    "for",
    "format",
    "fsutil",
    "ftype",
    "goto",
    "gpresult",
    "help",
    "icacls",
    "if",
    "label",
    "md",
    "mkdir",
    "mklink",
    "mode",
    "more",
    "move",
    "openfiles",
    "path",
    "pause",
    "popd",
    "print",
    "prompt",
    "pushd",
    "rd",
    "recover",
    "rem",
    "ren",
    "rename",
    "replace",
    "rmdir",
    "robocopy",
    "set",
    "setlocal",
    "sc",
    "schtasks",
    "shift",
    "shutdown",
    "sort",
    "start",
    "subst",
    "systeminfo",
    "tasklist",
    "taskkill",
    "time",
    "title",
    "tree",
    "type",
    "ver",
    "verify",
    "vol",
    "xcopy",
    "wmic",
}


def is_builtin(command: str) -> bool:
    """Check if a command is a Windows built-in command.

    Args:
        command: Name of the command to check

    Returns
    -------
        bool: True if the command is a built-in command, False otherwise.
    """
    return command.lower() in WINDOWS_BUILTINS


@dataclass
class FindResult:
    """Data class for find executable result."""

    name: str
    path: list[str] | str | None

    def __str__(self) -> str:
        """Return a string representation of the FindResult object.

        Returns
        -------
            str: String representation of the FindResult object.
        """
        if self.path:
            return f"[OK] {self.name} -> {self.path}"

        return f"[NOT FOUND] {self.name} -> not found"


def find_executable(name: str, *, fuzzy_mode: bool) -> FindResult:
    """Find executable path for commands cross-platform.

    Args:
        name: Name of the command to find
        fuzzy_mode: Whether to use fuzzy matching

    Returns
    -------
        Tuple containing the command name and its path if found, None otherwise.
    """
    # Select command based on system
    match_name = name if not fuzzy_mode else f"*{name}*{EXTENSION}"
    cmd = ["where" if IS_WINDOWS else "which", match_name]

    try:
        # Run command and capture output
        result = subprocess.run(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.DEVNULL,
            text=True,
            check=True,
        )
    except (subprocess.CalledProcessError, FileNotFoundError):  # Check direct executable path on UNIX systems
        if IS_WINDOWS and is_builtin(name):
            return FindResult(name, WINDOWS_CMD_PATH)

        if not IS_WINDOWS and os.access(f"/usr/bin/{name}", os.X_OK):
            return FindResult(name, f"/usr/bin/{name}")

        return FindResult(name, None)
    else:
        # Handle Windows multiple results case
        paths = [path.strip() for path in result.stdout.strip().split("\n") if path.strip()]

        if IS_WINDOWS:
            return FindResult(name, paths)

        return FindResult(name, result.stdout.strip())


def parse_args() -> argparse.Namespace:
    """Parse command-line arguments for the which command.

    Returns
    -------
        argparse.Namespace: Parsed command-line arguments.
    """
    parser = argparse.ArgumentParser(description="Find executable path for commands.")
    parser.add_argument("commands", type=str, nargs="+", help="Commands to query")
    parser.add_argument("-f", "--fuzzy", action="store_true", help="Enable fuzzy matching")
    return parser.parse_args()


def main() -> None:
    """Run entry point for the command-line interface."""
    args = parse_args()

    logger.info("Searching for executable paths...")
    t0 = time.perf_counter()
    for command in args.commands:
        logger.info(find_executable(command, fuzzy_mode=args.fuzzy))
    logger.info(f"Finished in {time.perf_counter() - t0:.4f}s")
