"""Subprocess wrappers for Homebrew CLI commands."""

import json
import subprocess
from typing import Any


class BrewError(Exception):
    """Raised when a brew command fails."""
    pass


def _run(args: list[str], timeout: int = 120) -> str:
    """Run a brew command and return stdout."""
    cmd = ["brew"] + args
    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout,
        )
    except FileNotFoundError:
        raise BrewError("Homebrew is not installed or not in PATH")
    except subprocess.TimeoutExpired:
        raise BrewError(f"Command timed out after {timeout}s: brew {' '.join(args)}")

    if result.returncode != 0:
        stderr = result.stderr.strip()
        raise BrewError(stderr or f"brew {' '.join(args)} failed with exit code {result.returncode}")

    return result.stdout.strip()


def _run_json(args: list[str], timeout: int = 120) -> Any:
    """Run a brew command with --json=v2 and parse output."""
    output = _run(args + ["--json=v2"], timeout=timeout)
    return json.loads(output) if output else {}


# --- Search & Info ---

def search(query: str, cask: bool = False) -> list[str]:
    args = ["search"]
    if cask:
        args.append("--cask")
    else:
        args.append("--formula")
    args.append(query)
    output = _run(args)
    return [line for line in output.splitlines() if line.strip()]


def info(package: str, cask: bool = False) -> dict:
    args = ["info", "--json=v2"]
    if cask:
        args.append("--cask")
    else:
        args.append("--formula")
    args.append(package)
    output = _run(args)
    return json.loads(output) if output else {}


def list_installed(cask: bool = False) -> list[str]:
    args = ["list", "--formula"]
    if cask:
        args = ["list", "--cask"]
    output = _run(args)
    return [line for line in output.splitlines() if line.strip()]


def list_outdated(cask: bool = False) -> list[dict]:
    args = ["outdated", "--json=v2"]
    if cask:
        args.append("--cask")
    else:
        args.append("--formula")
    output = _run(args)
    if not output:
        return []
    data = json.loads(output)
    if cask:
        return data.get("casks", [])
    return data.get("formulae", [])


def deps(package: str) -> str:
    return _run(["deps", "--tree", package])


# --- Install/Manage ---

def install(package: str, cask: bool = False) -> str:
    args = ["install"]
    if cask:
        args.append("--cask")
    args.append(package)
    return _run(args, timeout=300)


def uninstall(package: str, cask: bool = False) -> str:
    args = ["uninstall"]
    if cask:
        args.append("--cask")
    args.append(package)
    return _run(args, timeout=300)


def upgrade(package: str, cask: bool = False) -> str:
    args = ["upgrade"]
    if cask:
        args.append("--cask")
    args.append(package)
    return _run(args, timeout=300)


def upgrade_all() -> str:
    return _run(["upgrade"], timeout=600)


def cleanup(dry_run: bool = False) -> str:
    args = ["cleanup"]
    if dry_run:
        args.append("--dry-run")
    return _run(args, timeout=300)


# --- Taps ---

def tap(name: str) -> str:
    return _run(["tap", name], timeout=120)


def untap(name: str) -> str:
    return _run(["untap", name])


def list_taps() -> list[str]:
    output = _run(["tap"])
    return [line for line in output.splitlines() if line.strip()]


# --- Services ---

def list_services() -> list[dict]:
    output = _run(["services", "list"])
    lines = output.splitlines()
    if len(lines) < 2:
        return []
    headers = lines[0].lower().split()
    services = []
    for line in lines[1:]:
        parts = line.split(None, len(headers) - 1)
        if parts:
            service = {}
            for i, header in enumerate(headers):
                service[header] = parts[i] if i < len(parts) else ""
            services.append(service)
    return services


def start_service(name: str) -> str:
    return _run(["services", "start", name])


def stop_service(name: str) -> str:
    return _run(["services", "stop", name])


def restart_service(name: str) -> str:
    return _run(["services", "restart", name])


# --- System ---

def doctor() -> str:
    try:
        return _run(["doctor"], timeout=300)
    except BrewError as e:
        # doctor returns non-zero if there are warnings, but output is still useful
        return str(e)


def update() -> str:
    return _run(["update"], timeout=300)
