"""Typer-based CLI — the ``konstant`` entry point."""

from __future__ import annotations

import time
from pathlib import Path
from typing import Optional

import typer
from rich.console import Console
from rich.table import Table

from konstant.config import KonstantConfig, ServiceConfig
from konstant.logger import follow_log, pretty_print_log_line, tail_log
from konstant.process import ProcessManager
from konstant.state import StateManager
from konstant.utils import pid_alive

app = typer.Typer(
    name="konstant",
    help="Lightweight Python process manager.",
    no_args_is_help=True,
    add_completion=False,
)
console = Console()

_config_option = typer.Option(None, "--config", "-c", help="Path to konstant.toml")

_managers: dict[str, ProcessManager] = {}


def _load_config(config_path: Optional[Path]) -> KonstantConfig:
    try:
        return KonstantConfig.load(config_path)
    except (FileNotFoundError, ValueError) as exc:
        console.print(f"[red]Error:[/red] {exc}")
        raise typer.Exit(1) from exc


def _resolve_names(cfg: KonstantConfig, name: Optional[str]) -> list[str]:
    if name is not None:
        try:
            cfg.get_service(name)
        except KeyError as exc:
            console.print(f"[red]Error:[/red] {exc}")
            raise typer.Exit(1) from exc
        return [name]
    return cfg.service_names()


def _status_style(status: str) -> str:
    return {
        "running": "[green]running[/green]",
        "stopped": "[yellow]stopped[/yellow]",
        "errored": "[red]errored[/red]",
        "starting": "[cyan]starting[/cyan]",
    }.get(status, status)


# ------------------------------------------------------------------
# Commands
# ------------------------------------------------------------------


@app.command()
def start(
    name: Optional[str] = typer.Argument(None, help="Service name (omit for all)"),
    config: Optional[Path] = _config_option,
    foreground: bool = typer.Option(False, "--fg", help="Stay in foreground (block)"),
) -> None:
    """Start service(s)."""
    cfg = _load_config(config)
    names = _resolve_names(cfg, name)
    for svc_name in names:
        svc = cfg.get_service(svc_name)
        mgr = ProcessManager(svc)
        try:
            mgr.start()
        except RuntimeError as exc:
            console.print(f"[yellow]{exc}[/yellow]")
            continue
        _managers[svc_name] = mgr
        console.print(f"[green]Started[/green] {svc_name}")
    if foreground and _managers:
        console.print("[dim]Running in foreground — Ctrl+C to stop all[/dim]")
        try:
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            for mgr in _managers.values():
                mgr.stop()
            console.print("\n[yellow]All services stopped.[/yellow]")


@app.command()
def stop(
    name: Optional[str] = typer.Argument(None, help="Service name (omit for all)"),
    config: Optional[Path] = _config_option,
) -> None:
    """Stop service(s)."""
    cfg = _load_config(config)
    names = _resolve_names(cfg, name)
    for svc_name in names:
        mgr = _managers.pop(svc_name, None)
        if mgr is not None:
            mgr.stop()
            console.print(f"[yellow]Stopped[/yellow] {svc_name}")
            continue
        sm = StateManager(svc_name)
        state = sm.read()
        if state.pid and pid_alive(state.pid):
            from konstant.utils import kill_process_tree

            kill_process_tree(state.pid)
            sm.mark_stopped()
            console.print(f"[yellow]Stopped[/yellow] {svc_name}")
        else:
            console.print(f"[dim]{svc_name} is not running[/dim]")


@app.command()
def restart(
    name: Optional[str] = typer.Argument(None, help="Service name (omit for all)"),
    config: Optional[Path] = _config_option,
) -> None:
    """Restart service(s)."""
    cfg = _load_config(config)
    names = _resolve_names(cfg, name)
    for svc_name in names:
        mgr = _managers.pop(svc_name, None)
        if mgr is not None:
            mgr.stop()
        else:
            sm = StateManager(svc_name)
            state = sm.read()
            if state.pid and pid_alive(state.pid):
                from konstant.utils import kill_process_tree

                kill_process_tree(state.pid)
                sm.mark_stopped()
    time.sleep(0.5)
    for svc_name in names:
        svc = cfg.get_service(svc_name)
        mgr = ProcessManager(svc)
        try:
            mgr.start()
        except RuntimeError as exc:
            console.print(f"[yellow]{exc}[/yellow]")
            continue
        _managers[svc_name] = mgr
        console.print(f"[green]Restarted[/green] {svc_name}")


@app.command()
def status(
    name: Optional[str] = typer.Argument(None, help="Service name (omit for all)"),
    config: Optional[Path] = _config_option,
) -> None:
    """Show service status table."""
    cfg = _load_config(config)
    names = _resolve_names(cfg, name)
    table = Table(title="konstant services", show_lines=True)
    table.add_column("Service", style="bold")
    table.add_column("Status")
    table.add_column("PID")
    table.add_column("Uptime")
    table.add_column("Restarts", justify="right")
    table.add_column("Exit Code", justify="right")

    for svc_name in names:
        sm = StateManager(svc_name)
        st = sm.read()
        uptime = ""
        if st.started_at and st.status == "running":
            from datetime import datetime, timezone

            try:
                started = datetime.fromisoformat(st.started_at)
                delta = datetime.now(timezone.utc) - started
                mins, secs = divmod(int(delta.total_seconds()), 60)
                hours, mins = divmod(mins, 60)
                uptime = f"{hours}h {mins}m {secs}s"
            except ValueError:
                uptime = "?"
        alive = pid_alive(st.pid)
        displayed_status = st.status
        if st.status == "running" and not alive:
            displayed_status = "dead"
        table.add_row(
            svc_name,
            _status_style(displayed_status),
            str(st.pid) if st.pid else "-",
            uptime or "-",
            str(st.restart_count),
            str(st.exit_code) if st.exit_code is not None else "-",
        )
    console.print(table)


@app.command()
def logs(
    name: Optional[str] = typer.Argument(None, help="Service name (required)"),
    config: Optional[Path] = _config_option,
    follow: bool = typer.Option(False, "--follow", "-f", help="Stream live"),
    lines: int = typer.Option(50, "--lines", "-n", help="Number of lines to show"),
    raw: bool = typer.Option(False, "--raw", help="Show raw log lines without pretty-printing JSON"),
) -> None:
    """Tail or follow service logs."""
    if name is None:
        console.print("[red]Error:[/red] service name is required for logs")
        raise typer.Exit(1)
    cfg = _load_config(config)
    svc = cfg.get_service(name)
    is_json = svc.logging.format == "json"
    log_path = svc.resolved_cwd / "logs" / f"{name}.log"
    if not log_path.exists():
        console.print(f"[dim]No log file found at {log_path}[/dim]")
        raise typer.Exit(0)

    def _print_line(line: str) -> None:
        if is_json and not raw:
            print(pretty_print_log_line(line))
        else:
            print(line, end="")

    if follow:
        for line in tail_log(log_path, lines):
            _print_line(line)
        try:
            follow_log(log_path, formatter=None if raw or not is_json else pretty_print_log_line)
        except KeyboardInterrupt:
            pass
    else:
        for line in tail_log(log_path, lines):
            _print_line(line)


@app.command()
def install(
    name: Optional[str] = typer.Argument(None, help="Service name (omit for all)"),
    config: Optional[Path] = _config_option,
    user: Optional[str] = typer.Option(None, "--user", help="Service account (Windows only)"),
    password: Optional[str] = typer.Option(None, "--password", help="Service account password (Windows only)"),
) -> None:
    """Register service(s) as system services (survives reboot)."""
    from konstant.service import _platform_label, install_service

    cfg = _load_config(config)
    names = _resolve_names(cfg, name)
    label = _platform_label()
    for svc_name in names:
        svc = cfg.get_service(svc_name)
        try:
            install_service(svc, user=user, password=password, config_path=config)
            console.print(f"[green]Installed[/green] {svc_name} as {label}")
        except Exception as exc:
            console.print(f"[red]Failed to install {svc_name}:[/red] {exc}")


@app.command()
def uninstall(
    name: Optional[str] = typer.Argument(None, help="Service name (omit for all)"),
    config: Optional[Path] = _config_option,
) -> None:
    """Remove system service registration."""
    from konstant.service import uninstall_service

    cfg = _load_config(config)
    names = _resolve_names(cfg, name)
    for svc_name in names:
        try:
            uninstall_service(svc_name)
            console.print(f"[yellow]Uninstalled[/yellow] {svc_name}")
        except Exception as exc:
            console.print(f"[red]Failed to uninstall {svc_name}:[/red] {exc}")


if __name__ == "__main__":
    app()
