"""CUPS queue inspection, display, and interactive fix functions."""

from __future__ import annotations

from pathlib import Path
import re
import shutil
import subprocess
import sys
import time
from typing import TYPE_CHECKING

from python_pkg.brother_printer.constants import (
    BOLD,
    CYAN,
    DIM,
    GREEN,
    MIN_LPSTAT_JOB_PARTS,
    RED,
    RESET,
    YELLOW,
    _out,
    _prompt,
)
from python_pkg.brother_printer.cups_service import find_cups_printer_name
from python_pkg.brother_printer.data_classes import CUPSJob, CUPSQueueStatus

if TYPE_CHECKING:
    from collections.abc import Callable


# ── Queue inspection ─────────────────────────────────────────────────


def _parse_lpstat_printer_line(line: str) -> tuple[bool, str]:
    """Parse an lpstat -p line. Returns (enabled, reason)."""
    enabled = "disabled" not in line.lower()
    reason = ""
    match = re.search(r"\d{4}\s+-\s*(.+)", line)
    if match:
        reason = match.group(1).strip()
    return enabled, reason


def _parse_lpstat_jobs(output: str, printer_name: str) -> list[CUPSJob]:
    """Parse lpstat -o output into CUPSJob list."""
    jobs: list[CUPSJob] = []
    for line in output.splitlines():
        if not line.startswith(printer_name):
            continue
        parts = line.split()
        if len(parts) >= MIN_LPSTAT_JOB_PARTS:
            job_id = parts[0]
            user = parts[1]
            size = parts[2]
            date = " ".join(parts[3:])
            jobs.append(CUPSJob(job_id=job_id, user=user, size=size, date=date))
    return jobs


def get_cups_queue_status() -> CUPSQueueStatus:
    """Check if the CUPS queue is disabled and list pending jobs."""
    printer_name = find_cups_printer_name()
    if not printer_name:
        return CUPSQueueStatus()

    result = CUPSQueueStatus(printer_name=printer_name)
    lpstat_path = shutil.which("lpstat")
    if not lpstat_path:
        return result

    try:
        r = subprocess.run(
            [lpstat_path, "-p", printer_name],
            capture_output=True,
            text=True,
            timeout=5,
            check=False,
        )
        for line in r.stdout.splitlines():
            if "printer" in line.lower() and printer_name in line:
                result.enabled, result.reason = _parse_lpstat_printer_line(line)
                break
    except (subprocess.TimeoutExpired, subprocess.SubprocessError, OSError):
        pass

    try:
        r = subprocess.run(
            [lpstat_path, "-o", printer_name],
            capture_output=True,
            text=True,
            timeout=5,
            check=False,
        )
        result.jobs = _parse_lpstat_jobs(r.stdout, printer_name)
    except (subprocess.TimeoutExpired, subprocess.SubprocessError, OSError):
        pass

    has_errors, last_error = _check_cups_backend_errors(printer_name)
    result.has_backend_errors = has_errors
    result.last_backend_error = last_error

    return result


# ── CUPS fix actions ─────────────────────────────────────────────────


def _cups_enable_printer(printer_name: str) -> bool:
    """Re-enable a disabled CUPS printer. Returns True on success."""
    cupsenable_path = shutil.which("cupsenable")
    if not cupsenable_path:
        _out(f"  {RED}cupsenable not found.{RESET}")
        return False
    try:
        subprocess.run(
            [cupsenable_path, printer_name],
            timeout=5,
            check=True,
        )
    except (subprocess.TimeoutExpired, subprocess.CalledProcessError, OSError) as e:
        _out(f"  {RED}Failed to enable printer: {e}{RESET}")
        return False
    else:
        return True


def _cups_cancel_all_jobs(printer_name: str) -> bool:
    """Cancel all pending jobs. Returns True on success."""
    cancel_path = shutil.which("cancel")
    if not cancel_path:
        _out(f"  {RED}cancel command not found.{RESET}")
        return False
    try:
        subprocess.run(
            [cancel_path, "-a", printer_name],
            timeout=5,
            check=True,
        )
    except (subprocess.TimeoutExpired, subprocess.CalledProcessError, OSError) as e:
        _out(f"  {RED}Failed to cancel jobs: {e}{RESET}")
        return False
    else:
        return True


def _cups_cancel_job(job_id: str) -> bool:
    """Cancel a specific job. Returns True on success."""
    cancel_path = shutil.which("cancel")
    if not cancel_path:
        return False
    try:
        subprocess.run(
            [cancel_path, job_id],
            timeout=5,
            check=True,
        )
    except (subprocess.TimeoutExpired, subprocess.CalledProcessError, OSError):
        return False
    else:
        return True


def _cups_restart_service() -> bool:
    """Restart the CUPS service. Returns True on success."""
    systemctl_path = shutil.which("systemctl")
    if not systemctl_path:
        _out(f"  {RED}systemctl not found.{RESET}")
        return False
    sys.stdout.write(f"  {DIM}Restarting CUPS...{RESET}")
    sys.stdout.flush()
    try:
        proc = subprocess.Popen(
            [systemctl_path, "restart", "cups"],
        )
        deadline = time.time() + 30
        while proc.poll() is None:
            if time.time() > deadline:
                proc.kill()
                proc.wait()
                sys.stdout.write("\n")
                _out(f"  {RED}CUPS restart timed out (stuck backend process?).{RESET}")
                _out(
                    f"  {DIM}Try: sudo kill -9 $(pgrep -f 'cups/backend/usb')"
                    f" && sudo systemctl restart cups{RESET}"
                )
                return False
            sys.stdout.write(".")
            sys.stdout.flush()
            time.sleep(1)
        sys.stdout.write("\n")
        if proc.returncode != 0:
            _out(f"  {RED}CUPS restart failed (exit code {proc.returncode}).{RESET}")
            return False
    except OSError as e:
        sys.stdout.write("\n")
        _out(f"  {RED}Failed to restart CUPS: {e}{RESET}")
        return False
    time.sleep(2)
    return True


# ── Backend error detection ──────────────────────────────────────────


def _is_cups_printer_healthy(printer_name: str) -> bool:
    """Check live CUPS state via lpstat. Returns True if enabled with no issues."""
    lpstat_path = shutil.which("lpstat")
    if not lpstat_path:
        return False
    try:
        r = subprocess.run(
            [lpstat_path, "-p", printer_name],
            capture_output=True,
            text=True,
            timeout=5,
            check=False,
        )
        for line in r.stdout.splitlines():
            if (
                printer_name in line
                and "idle" in line.lower()
                and "enabled" in line.lower()
            ):
                return True
    except (subprocess.TimeoutExpired, subprocess.SubprocessError, OSError):
        pass
    return False


def _find_backend_error_in_log(
    lines: list[str],
) -> tuple[str, str, str]:
    """Scan CUPS log lines (reversed) for backend errors.

    Returns:
        (backend_error, error_timestamp, last_success_timestamp)
    """
    backend_error = ""
    error_timestamp = ""
    last_success_timestamp = ""

    for line in reversed(lines):
        if (
            "backend errors" in line or "stopped with status" in line
        ) and not backend_error:
            backend_error = line.strip()
            ts_match = re.search(r"\[([^\]]+)\]", line)
            if ts_match:
                error_timestamp = ts_match.group(1)
        if ("Completed" in line or "total" in line) and error_timestamp:
            ts_match = re.search(r"\[([^\]]+)\]", line)
            if ts_match:
                last_success_timestamp = ts_match.group(1)
                break

    return backend_error, error_timestamp, last_success_timestamp


def _check_cups_backend_errors(
    printer_name: str,
) -> tuple[bool, str]:
    """Check CUPS error log for backend errors. Returns (has_errors, last_error)."""
    if _is_cups_printer_healthy(printer_name):
        return False, ""

    log_path = Path("/var/log/cups/error_log")
    if not log_path.exists():
        return False, ""
    try:
        lines = log_path.read_text(encoding="utf-8", errors="replace").splitlines()
    except OSError:
        return False, ""

    backend_error, error_timestamp, last_success_timestamp = _find_backend_error_in_log(
        lines
    )

    if not backend_error:
        return False, ""

    if last_success_timestamp and last_success_timestamp > error_timestamp:
        return False, ""

    return True, backend_error


# ── Queue status display ────────────────────────────────────────────


def display_cups_queue_status(queue: CUPSQueueStatus) -> None:
    """Display CUPS queue status and offer interactive fixes."""
    if not queue.printer_name:
        return
    if queue.enabled and not queue.jobs and not queue.has_backend_errors:
        return

    _out()
    _out(f"{BOLD}── Print Queue ──{RESET}")
    _out()

    if queue.has_backend_errors and queue.enabled and not queue.jobs:
        _out(f"  {YELLOW}{BOLD}⚡ CUPS backend has stale errors{RESET}")
        _out(
            f"  {DIM}New print jobs may silently fail."
            f" A CUPS restart usually fixes this.{RESET}"
        )
        _out()

    if not queue.enabled:
        _out(f"  {RED}{BOLD}⚠  Printer queue is DISABLED{RESET}")
        if queue.reason:
            _out(f"  {DIM}Reason: {queue.reason}{RESET}")
        _out()

    if queue.jobs:
        _out(f"  {BOLD}Pending jobs ({len(queue.jobs)}):{RESET}")
        for job in queue.jobs:
            _out(f"    {job.job_id}  {DIM}{job.user}  {job.size}B  {job.date}{RESET}")
        _out()

    _offer_queue_fix(queue)


# ── Interactive queue fix ────────────────────────────────────────────


def _offer_queue_fix(queue: CUPSQueueStatus) -> None:
    """Prompt the user to fix a disabled queue / pending jobs."""
    _out(f"  {BOLD}Available actions:{RESET}")

    options: list[str] = []
    if not queue.enabled and queue.jobs:
        _out(f"    {CYAN}1){RESET} Re-enable printer and retry all jobs")
        _out(f"    {CYAN}2){RESET} Re-enable printer and cancel all jobs")
        _out(f"    {CYAN}3){RESET} Cancel all jobs (keep printer disabled)")
        _out(f"    {CYAN}4){RESET} Restart CUPS service (fixes stale backend)")
        _out(f"    {CYAN}5){RESET} Restart CUPS + re-enable + retry all jobs")
        _out(f"    {CYAN}6){RESET} Do nothing")
        options = ["1", "2", "3", "4", "5", "6"]
    elif not queue.enabled:
        _out(f"    {CYAN}1){RESET} Re-enable printer")
        _out(f"    {CYAN}2){RESET} Restart CUPS service (fixes stale backend)")
        _out(f"    {CYAN}3){RESET} Do nothing")
        options = ["1", "2", "3"]
    elif queue.jobs:
        _out(f"    {CYAN}1){RESET} Cancel all pending jobs")
        _out(f"    {CYAN}2){RESET} Restart CUPS service (fixes stale backend)")
        _out(f"    {CYAN}3){RESET} Do nothing")
        options = ["1", "2", "3"]
    else:
        _out(f"    {CYAN}1){RESET} Restart CUPS service (fixes stale backend)")
        _out(f"    {CYAN}2){RESET} Do nothing")
        options = ["1", "2"]

    _out()
    choice = _prompt(f"  Choose [{'/'.join(options)}]: ")
    _out()

    if not queue.enabled and queue.jobs:
        _handle_disabled_with_jobs(queue, choice)
    elif not queue.enabled:
        _handle_disabled_no_jobs(queue, choice)
    elif queue.jobs:
        _handle_enabled_with_jobs(queue, choice)
    else:
        _handle_backend_errors_only(choice)


def _dwj_enable_only(printer_name: str) -> None:
    """Choice 1: re-enable printer so queued jobs are retried."""
    if _cups_enable_printer(printer_name):
        _out(f"  {GREEN}✓ Printer re-enabled. Jobs will be retried.{RESET}")


def _dwj_cancel_and_enable(printer_name: str) -> None:
    """Choice 2: cancel all jobs then re-enable."""
    _cups_cancel_all_jobs(printer_name)
    if _cups_enable_printer(printer_name):
        _out(f"  {GREEN}✓ All jobs cancelled and printer re-enabled.{RESET}")


def _dwj_cancel_only(printer_name: str) -> None:
    """Choice 3: cancel all jobs."""
    if _cups_cancel_all_jobs(printer_name):
        _out(f"  {GREEN}✓ All jobs cancelled.{RESET}")


def _dwj_restart_only(_printer_name: str) -> None:
    """Choice 4: restart CUPS."""
    if _cups_restart_service():
        _out(f"  {GREEN}✓ CUPS restarted.{RESET}")


def _dwj_restart_and_enable(printer_name: str) -> None:
    """Choice 5: restart CUPS and re-enable printer."""
    if _cups_restart_service():
        _cups_enable_printer(printer_name)
        _out(
            f"  {GREEN}✓ CUPS restarted, printer re-enabled."
            f" Jobs will be retried.{RESET}"
        )


_DWJ_ACTIONS: dict[str, Callable[[str], None]] = {
    "1": _dwj_enable_only,
    "2": _dwj_cancel_and_enable,
    "3": _dwj_cancel_only,
    "4": _dwj_restart_only,
    "5": _dwj_restart_and_enable,
}


def _handle_disabled_with_jobs(queue: CUPSQueueStatus, choice: str) -> None:
    """Handle fix for disabled printer with pending jobs."""
    action = _DWJ_ACTIONS.get(choice)
    if action is not None:
        action(queue.printer_name)
    else:
        _out(f"  {DIM}No changes made.{RESET}")


def _handle_disabled_no_jobs(queue: CUPSQueueStatus, choice: str) -> None:
    """Handle fix for disabled printer with no pending jobs."""
    if choice == "1":
        if _cups_enable_printer(queue.printer_name):
            _out(f"  {GREEN}✓ Printer re-enabled.{RESET}")
    elif choice == "2":
        if _cups_restart_service():
            _cups_enable_printer(queue.printer_name)
            _out(f"  {GREEN}✓ CUPS restarted and printer re-enabled.{RESET}")
    else:
        _out(f"  {DIM}No changes made.{RESET}")


def _handle_enabled_with_jobs(queue: CUPSQueueStatus, choice: str) -> None:
    """Handle fix for enabled printer with stuck jobs."""
    if choice == "1":
        if _cups_cancel_all_jobs(queue.printer_name):
            _out(f"  {GREEN}✓ All jobs cancelled.{RESET}")
    elif choice == "2":
        if _cups_restart_service():
            _out(f"  {GREEN}✓ CUPS restarted.{RESET}")
    else:
        _out(f"  {DIM}No changes made.{RESET}")


def _handle_backend_errors_only(choice: str) -> None:
    """Handle fix when only stale backend errors are detected."""
    if choice == "1":
        if _cups_restart_service():
            _out(f"  {GREEN}✓ CUPS restarted. Stale backend errors cleared.{RESET}")
    else:
        _out(f"  {DIM}No changes made.{RESET}")
