"""``ilum top`` command — resource usage per module."""

from __future__ import annotations

import time
from dataclasses import dataclass
from typing import Any

import typer

from ilum.cli.defaults import resolve_profile_defaults
from ilum.core.kubernetes import KubeClient, PodResources
from ilum.core.modules import MODULE_REGISTRY, ModuleResolver
from ilum.errors import IlumError
from ilum.wizard.deps import ensure_tools


@dataclass
class ModuleUsage:
    """Aggregated resource usage for a single module."""

    name: str
    pod_count: int
    cpu_used: int  # millicores
    cpu_requested: int  # millicores
    mem_used: int  # bytes
    mem_requested: int  # bytes


def _format_cpu(millicores: int) -> str:
    """Format CPU millicores as a human-readable string."""
    if millicores >= 1000:
        return f"{millicores / 1000:.1f}"
    return f"{millicores}m"


def _format_mem(mem_bytes: int) -> str:
    """Format memory bytes as a human-readable string."""
    if mem_bytes >= 1024 * 1024 * 1024:
        return f"{mem_bytes / (1024 * 1024 * 1024):.1f}Gi"
    if mem_bytes >= 1024 * 1024:
        return f"{mem_bytes / (1024 * 1024):.0f}Mi"
    if mem_bytes >= 1024:
        return f"{mem_bytes / 1024:.0f}Ki"
    return f"{mem_bytes}B"


def _pct(used: int, requested: int) -> str:
    """Compute a percentage string, or '—' if no request."""
    if requested == 0:
        return "\u2014"
    return f"{(used / requested) * 100:.0f}%"


def _map_pod_to_module(pod_name: str, label_map: dict[str, str]) -> str:
    """Guess the module a pod belongs to from its name."""
    for label_prefix, mod_name in label_map.items():
        if label_prefix in pod_name:
            return mod_name
    return ""


def _build_label_map() -> dict[str, str]:
    """Build a mapping from pod name fragments to module names."""
    label_map: dict[str, str] = {}
    for mod in MODULE_REGISTRY:
        if mod.pod_label and "=" in mod.pod_label:
            label_value = mod.pod_label.split("=", 1)[1]
            label_map[label_value] = mod.name
    return label_map


def _collect_usage(
    k8s: KubeClient,
    namespace: str,
    module_filter: str | None = None,
) -> list[ModuleUsage]:
    """Collect and aggregate resource usage per module."""
    metrics = k8s.list_pod_metrics(namespace)
    resources = k8s.list_pod_resources(namespace)
    label_map = _build_label_map()

    # Index resources by pod name
    res_by_name: dict[str, PodResources] = {r.name: r for r in resources}

    # Aggregate by module
    usage_map: dict[str, ModuleUsage] = {}
    for m in metrics:
        mod_name = _map_pod_to_module(m.name, label_map)
        if not mod_name:
            mod_name = "(other)"
        if module_filter and mod_name != module_filter:
            continue

        if mod_name not in usage_map:
            usage_map[mod_name] = ModuleUsage(
                name=mod_name,
                pod_count=0,
                cpu_used=0,
                cpu_requested=0,
                mem_used=0,
                mem_requested=0,
            )

        u = usage_map[mod_name]
        u.pod_count += 1
        u.cpu_used += m.cpu_millicores
        u.mem_used += m.memory_bytes

        res = res_by_name.get(m.name)
        if res:
            u.cpu_requested += res.cpu_request_millicores
            u.mem_requested += res.memory_request_bytes

    return sorted(usage_map.values(), key=lambda u: u.name)


def top(
    module: str | None = typer.Argument(None, help="Filter to a specific module."),
    release: str | None = typer.Option(
        None, "--release", "-r", show_default="from profile", help="Helm release name."
    ),
    namespace: str | None = typer.Option(
        None, "--namespace", "-n", show_default="from profile", help="Kubernetes namespace."
    ),
    context: str | None = typer.Option(
        None, "--context", show_default="from profile", help="Kubernetes context."
    ),
    sort_by: str = typer.Option("name", "--sort-by", help="Sort by: name, cpu, memory."),
    watch: bool = typer.Option(False, "--watch", "-w", help="Continuously refresh."),
    interval: int = typer.Option(5, "--interval", "-i", help="Refresh interval in seconds."),
) -> None:
    """Show resource usage per Ilum module."""
    release, namespace, context = resolve_profile_defaults(release, namespace, context)
    import ilum.cli.output as output_mod

    console = output_mod.console
    from ilum.cli.formatters import CommandResult, OutputFormat, ResultFormatter

    fmt = OutputFormat(console.output_format)

    try:
        ensure_tools(["kubectl"], console)
        k8s = KubeClient(kubecontext=context)

        # Check metrics availability
        if not k8s.metrics_available():
            console.error(
                "Metrics API not available. Install metrics-server:\n"
                "  kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/"
                "releases/latest/download/components.yaml"
            )
            raise typer.Exit(code=1)

        # Validate module filter
        if module:
            ModuleResolver().get(module)  # raises ModuleError if unknown

        def _render_once() -> list[ModuleUsage]:
            usages = _collect_usage(k8s, namespace, module)

            # Sort
            if sort_by == "cpu":
                usages.sort(key=lambda u: u.cpu_used, reverse=True)
            elif sort_by == "memory":
                usages.sort(key=lambda u: u.mem_used, reverse=True)
            # else: already sorted by name

            return usages

        def _to_data(usages: list[ModuleUsage]) -> list[dict[str, Any]]:
            rows: list[dict[str, Any]] = []
            for u in usages:
                rows.append(
                    {
                        "module": u.name,
                        "pods": u.pod_count,
                        "cpu_used": _format_cpu(u.cpu_used),
                        "cpu_req": _format_cpu(u.cpu_requested),
                        "cpu_pct": _pct(u.cpu_used, u.cpu_requested),
                        "mem_used": _format_mem(u.mem_used),
                        "mem_req": _format_mem(u.mem_requested),
                        "mem_pct": _pct(u.mem_used, u.mem_requested),
                    }
                )
            # Total row
            total_cpu_used = sum(u.cpu_used for u in usages)
            total_cpu_req = sum(u.cpu_requested for u in usages)
            total_mem_used = sum(u.mem_used for u in usages)
            total_mem_req = sum(u.mem_requested for u in usages)
            rows.append(
                {
                    "module": "TOTAL",
                    "pods": sum(u.pod_count for u in usages),
                    "cpu_used": _format_cpu(total_cpu_used),
                    "cpu_req": _format_cpu(total_cpu_req),
                    "cpu_pct": _pct(total_cpu_used, total_cpu_req),
                    "mem_used": _format_mem(total_mem_used),
                    "mem_req": _format_mem(total_mem_req),
                    "mem_pct": _pct(total_mem_used, total_mem_req),
                }
            )
            return rows

        usages = _render_once()

        if fmt != OutputFormat.TABLE:
            data = _to_data(usages)
            cmd_result = CommandResult(
                data=data,
                summary=f"{len(usages)} modules",
                columns=[
                    "module",
                    "pods",
                    "cpu_used",
                    "cpu_req",
                    "cpu_pct",
                    "mem_used",
                    "mem_req",
                    "mem_pct",
                ],
            )
            ResultFormatter().format(
                cmd_result,
                fmt,
                console,
                no_headers=console.no_headers,
                field_selector=console.field_selector,
            )
            return

        def _render_table(usages: list[ModuleUsage]) -> None:
            columns = [
                "Module",
                "Pods",
                "CPU Used",
                "CPU Req",
                "CPU %",
                "Mem Used",
                "Mem Req",
                "Mem %",
            ]
            rows: list[list[str]] = []
            for u in usages:
                rows.append(
                    [
                        u.name,
                        str(u.pod_count),
                        _format_cpu(u.cpu_used),
                        _format_cpu(u.cpu_requested),
                        _pct(u.cpu_used, u.cpu_requested),
                        _format_mem(u.mem_used),
                        _format_mem(u.mem_requested),
                        _pct(u.mem_used, u.mem_requested),
                    ]
                )
            # Total row
            total_cpu_used = sum(u.cpu_used for u in usages)
            total_cpu_req = sum(u.cpu_requested for u in usages)
            total_mem_used = sum(u.mem_used for u in usages)
            total_mem_req = sum(u.mem_requested for u in usages)
            rows.append(
                [
                    "[bold]TOTAL[/bold]",
                    str(sum(u.pod_count for u in usages)),
                    _format_cpu(total_cpu_used),
                    _format_cpu(total_cpu_req),
                    _pct(total_cpu_used, total_cpu_req),
                    _format_mem(total_mem_used),
                    _format_mem(total_mem_req),
                    _pct(total_mem_used, total_mem_req),
                ]
            )
            console.table("Resource Usage", columns, rows)

        if not watch:
            _render_table(usages)
            return

        # Watch mode
        try:
            while True:
                _render_table(usages)
                time.sleep(interval)
                usages = _render_once()
        except KeyboardInterrupt:
            pass

    except IlumError as exc:
        console.handle_error(exc)
        raise typer.Exit(code=1) from exc
