"""Click CLI: cleanup + upgrade commands."""

from __future__ import annotations

import logging
import sys
from datetime import datetime, timezone
from pathlib import Path

import click
from dotenv import load_dotenv
from rich.console import Console
from rich.progress import Progress
from rich.table import Table

from .client import CnMaestroClient, CnMaestroError
from .devices import (
    Device,
    delete_device,
    list_devices,
    partition_for_cleanup,
)
from .firmware import (
    list_firmware,
    plan_upgrades,
    submit_upgrade,
)

LOG_PATH = Path("cnmaestro.log")
console = Console()


def _setup_audit_log() -> logging.Logger:
    log = logging.getLogger("cnmaestro.audit")
    if log.handlers:
        return log
    log.setLevel(logging.INFO)
    handler = logging.FileHandler(LOG_PATH)
    handler.setFormatter(
        logging.Formatter("%(asctime)s %(levelname)s %(message)s")
    )
    log.addHandler(handler)
    return log


def _fmt_last_sync(d: Device) -> str:
    if d.last_sync is None:
        return "[red]never[/red]"
    delta = datetime.now(tz=timezone.utc) - d.last_sync
    if delta.days >= 1:
        return f"{delta.days}d ago"
    hours = delta.seconds // 3600
    mins = (delta.seconds % 3600) // 60
    if hours:
        return f"{hours}h{mins:02d}m ago"
    return f"{mins}m ago"


def _render_cleanup_table(devices: list[Device], title: str) -> Table:
    table = Table(title=title, show_lines=False)
    table.add_column("Name")
    table.add_column("MAC")
    table.add_column("Product")
    table.add_column("Last sync")
    table.add_column("Tower")
    for d in sorted(devices, key=lambda x: (x.product, x.name)):
        table.add_row(
            d.name,
            d.mac,
            d.product,
            _fmt_last_sync(d),
            d.tower or "-",
        )
    return table


@click.group()
def main() -> None:
    """cnMaestro Cloud CLI."""
    load_dotenv()


@main.command()
@click.option(
    "--threshold-hours", type=float, default=24.0, show_default=True,
    help="Devices offline at least this long are removal candidates.",
)
@click.option("--dry-run", is_flag=True, help="Show plan, do not delete.")
@click.option("--yes", is_flag=True, help="Skip confirmation prompt.")
@click.option(
    "--force", is_flag=True,
    help="Override the >50%-of-fleet sanity check.",
)
def cleanup(threshold_hours: float, dry_run: bool, yes: bool, force: bool) -> None:
    """Remove devices offline > threshold and never-synced devices."""
    audit = _setup_audit_log()
    try:
        with CnMaestroClient() as client:
            console.print("Fetching device inventory...")
            devices = list_devices(client)
            partition = partition_for_cleanup(devices, threshold_hours)
            candidates = partition.candidates

            console.print(
                f"\n[bold]Fleet:[/bold] {partition.total} devices "
                f"([green]{len(partition.online)} online[/green], "
                f"[yellow]{len(partition.offline_recent)} offline (recent)[/yellow], "
                f"[red]{len(partition.offline_stale)} offline >{threshold_hours:g}h[/red], "
                f"[red]{len(partition.never_synced)} never-synced[/red])"
            )

            if not candidates:
                console.print("[green]Nothing to remove.[/green]")
                return

            console.print(_render_cleanup_table(candidates, "Removal candidates"))
            console.print(
                f"[bold red]Will remove {len(candidates)} devices[/bold red] "
                f"({len(partition.offline_stale)} offline-stale, "
                f"{len(partition.never_synced)} never-synced)."
            )

            ratio = len(candidates) / partition.total if partition.total else 0
            if ratio > 0.5 and not force:
                console.print(
                    f"[red]Refusing: {ratio:.0%} of the fleet would be deleted. "
                    f"Pass --force to override.[/red]"
                )
                sys.exit(2)

            if dry_run:
                console.print("[yellow]--dry-run: no changes made.[/yellow]")
                return

            if not yes and not click.confirm("Proceed with deletion?", default=False):
                console.print("Aborted.")
                return

            failures: list[tuple[Device, str]] = []
            with Progress() as progress:
                task = progress.add_task("Deleting", total=len(candidates))
                for d in candidates:
                    try:
                        delete_device(client, d.mac)
                        audit.info(
                            "DELETE device mac=%s name=%s product=%s",
                            d.mac, d.name, d.product,
                        )
                    except CnMaestroError as e:
                        failures.append((d, str(e)))
                        audit.error("DELETE failed mac=%s err=%s", d.mac, e)
                    progress.update(task, advance=1)

            console.print(
                f"[green]Deleted {len(candidates) - len(failures)}[/green]"
                + (f", [red]{len(failures)} failed[/red]" if failures else "")
            )
            for d, err in failures:
                console.print(f"  [red]✗ {d.mac} {d.name}: {err}[/red]")
    except CnMaestroError as e:
        console.print(f"[red]Error:[/red] {e}")
        sys.exit(1)


@main.command()
@click.option("--dry-run", is_flag=True, help="Show plan, do not submit jobs.")
@click.option("--yes", is_flag=True, help="Skip confirmation prompt.")
@click.option(
    "--product", default=None,
    help="Comma-separated product types to include (e.g. ePMP,PMP,cnMatrix).",
)
def upgrade(dry_run: bool, yes: bool, product: str | None) -> None:
    """Submit firmware-upgrade jobs to bring online devices to latest."""
    audit = _setup_audit_log()
    products_filter = (
        {p.strip() for p in product.split(",") if p.strip()} if product else None
    )
    try:
        with CnMaestroClient() as client:
            console.print("Fetching devices and firmware images...")
            devices = list_devices(client)
            images = list_firmware(client)
            groups = plan_upgrades(devices, images, products_filter=products_filter)

            online_count = sum(1 for d in devices if d.is_online)
            console.print(
                f"[bold]Online devices:[/bold] {online_count}; "
                f"[bold]upgrade groups:[/bold] {len(groups)}"
            )

            if not groups:
                console.print("[green]All eligible devices are already up to date.[/green]")
                return

            table = Table(title="Planned firmware jobs")
            table.add_column("Product")
            table.add_column("Target version")
            table.add_column("Package")
            table.add_column("Devices", justify="right")
            total_devices = 0
            for g in groups:
                table.add_row(
                    g.product,
                    g.target.version,
                    g.target.name,
                    str(len(g.devices)),
                )
                total_devices += len(g.devices)
            console.print(table)
            console.print(
                f"[bold]Total devices to upgrade:[/bold] {total_devices}"
            )

            if dry_run:
                console.print("[yellow]--dry-run: no jobs submitted.[/yellow]")
                return

            if not yes and not click.confirm(
                "Submit firmware jobs?", default=False
            ):
                console.print("Aborted.")
                return

            for g in groups:
                console.print(
                    f"Submitting {g.product} → {g.target.version} "
                    f"({len(g.devices)} devices)..."
                )
                try:
                    results = submit_upgrade(client, g)
                    for r in results:
                        job_id = (
                            r.get("job_id") or r.get("id") or r.get("data", {}).get("id")
                        )
                        console.print(f"  [green]job submitted[/green] id={job_id}")
                        audit.info(
                            "UPGRADE job product=%s target=%s devices=%d job_id=%s",
                            g.product, g.target.version, len(g.devices), job_id,
                        )
                except CnMaestroError as e:
                    console.print(f"  [red]✗ failed: {e}[/red]")
                    audit.error(
                        "UPGRADE job failed product=%s target=%s err=%s",
                        g.product, g.target.version, e,
                    )
    except CnMaestroError as e:
        console.print(f"[red]Error:[/red] {e}")
        sys.exit(1)


if __name__ == "__main__":
    main()
