"""Progress-aware Helm execution — polls pod status during long operations."""

from __future__ import annotations

import threading
import time
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ilum.cli.output import IlumConsole
    from ilum.core.kubernetes import KubeClient, PodStatus
    from ilum.core.release import HelmResult, ReleasePlan, ReleaseManager

POLL_INTERVAL: float = 5.0  # seconds between pod status checks
READINESS_TIMEOUT: float = 600.0  # max seconds to wait for pods after Helm returns
SETTLE_TIMEOUT: float = 30.0  # max seconds to wait for new pods to appear


def _fetch_active_pods(k8s: KubeClient, namespace: str) -> list[PodStatus] | None:
    """Fetch pods and filter to active (non-Job) ones. None on error."""
    try:
        pods = k8s.list_pods(namespace)
    except Exception:
        return None
    return [p for p in pods if p.phase not in ("Succeeded", "Failed")]


def _summarise_active(active: list[PodStatus]) -> str:
    """Build a one-line summary from an already-filtered active pod list."""
    if not active:
        return "Pods: waiting for pods to start"

    total = len(active)
    ready = sum(1 for p in active if p.ready and p.phase == "Running")
    pending = sum(1 for p in active if p.phase == "Pending")
    init = sum(1 for p in active if p.phase == "Running" and not p.ready)
    restarts = sum(p.restart_count for p in active)

    parts = [f"Pods: {ready}/{total} ready"]
    if pending:
        parts.append(f"{pending} pending")
    if init:
        parts.append(f"{init} initialising")
    if restarts:
        parts.append(f"{restarts} restart{'s' if restarts != 1 else ''}")

    return ", ".join(parts)


def _is_all_ready(active: list[PodStatus]) -> bool:
    """Return True when every pod in the list is Running and ready."""
    return bool(active) and all(p.ready and p.phase == "Running" for p in active)


def _summarise_pods(k8s: KubeClient, namespace: str) -> str:
    """Build a one-line pod status summary, e.g. 'Pods: 5/12 ready, 3 pending'.

    Returns empty string if pods cannot be fetched (best-effort).
    """
    active = _fetch_active_pods(k8s, namespace)
    if active is None:
        return ""
    return _summarise_active(active)


def execute_with_progress(
    mgr: ReleaseManager,
    plan: ReleasePlan,
    console: IlumConsole,
    message: str = "Working...",
    poll_interval: float = POLL_INTERVAL,
    readiness_timeout: float = READINESS_TIMEOUT,
    settle_timeout: float = SETTLE_TIMEOUT,
) -> HelmResult:
    """Run ``mgr.execute(plan)`` then wait for all pods to become ready.

    Phase 1: Helm runs in a background thread while the spinner shows pod
    status.  Without ``--wait`` Helm returns almost immediately.

    Phase 2a (settle): Wait for the pod landscape to change — new pods
    appearing or existing pods restarting.  This prevents declaring success
    before Kubernetes has started creating new resources.

    Phase 2b (readiness): Once new activity is detected (or *settle_timeout*
    expires), wait until every active pod is Running+ready.

    Each loop iteration fetches pods exactly once to avoid race conditions
    between the readiness check and the summary display.
    """
    result_box: list[HelmResult] = []
    error_box: list[Exception] = []

    # Snapshot active pod names before Helm runs
    baseline_names: set[str] = set()
    baseline = _fetch_active_pods(mgr.k8s, plan.namespace)
    if baseline is not None:
        baseline_names = {p.name for p in baseline}

    def _helm_worker() -> None:
        try:
            result_box.append(mgr.execute(plan))
        except Exception as exc:
            error_box.append(exc)

    thread = threading.Thread(target=_helm_worker, daemon=True)

    with console.progress_spinner(message) as handle:
        thread.start()

        # Phase 1 — while Helm is running
        while thread.is_alive():
            thread.join(timeout=poll_interval)
            if thread.is_alive():
                active = _fetch_active_pods(mgr.k8s, plan.namespace)
                if active is not None:
                    handle.update(f"{message} ({_summarise_active(active)})")

        # Propagate Helm errors immediately
        if error_box:
            raise error_box[0]

        # Phase 2a — wait for pod landscape to change (new/restarted pods)
        settle_deadline = time.monotonic() + settle_timeout

        while time.monotonic() < settle_deadline:
            active = _fetch_active_pods(mgr.k8s, plan.namespace)
            if active is not None:
                current_names = {p.name for p in active}
                if current_names != baseline_names:
                    break
                handle.update(f"{message} ({_summarise_active(active)})")
            time.sleep(poll_interval)

        # Phase 2b — wait for all pods to become ready
        deadline = time.monotonic() + readiness_timeout

        while time.monotonic() < deadline:
            active = _fetch_active_pods(mgr.k8s, plan.namespace)
            if active is not None:
                if _is_all_ready(active):
                    break
                handle.update(f"{message} ({_summarise_active(active)})")
            time.sleep(poll_interval)
        else:
            active = _fetch_active_pods(mgr.k8s, plan.namespace)
            summary = _summarise_active(active) if active is not None else ""
            console.warning(
                f"Timed out waiting for pods to become ready after "
                f"{int(readiness_timeout)}s. {summary}"
            )

    return result_box[0]
