"""E2E: doctor + setup + RBAC on a real cluster.

Verifies that doctor/setup commands run without errors and
produce meaningful results on a real K8s cluster.  Also checks
Phase 11 RBAC permission reporting.

Run with: pytest tests/e2e/test_doctor_setup.py -v -s
"""

from __future__ import annotations

from typing import Any

import pytest
from click.testing import CliRunner

from kuberoku.cli.clusters import clusters as clusters_group
from kuberoku.factory import KuberokuFactory
from tests.e2e.helpers import (
    cleanup_namespace_fixture,
    make_real_factory,
    skip_if_no_cluster,
)


@pytest.fixture(scope="module")
def real_factory() -> KuberokuFactory:
    return make_real_factory("e2e-doctor")


@pytest.fixture(scope="module", autouse=True)
def cleanup_namespace(real_factory: KuberokuFactory) -> Any:
    yield
    cleanup_namespace_fixture(real_factory)


def test_00_skip_if_no_cluster(real_factory: KuberokuFactory) -> None:
    skip_if_no_cluster()


def test_01_doctor_runs(real_factory: KuberokuFactory) -> None:
    """Doctor returns results without crash."""
    results = real_factory.doctor.run_checks()
    assert len(results) > 0
    for r in results:
        status = "PASS" if r.passed else "FAIL"
        print(f"  [{status}] {r.name}: {r.message}")


def test_02_doctor_namespace_check(real_factory: KuberokuFactory) -> None:
    """namespace_access check passes after namespace creation."""
    # Ensure namespace exists
    real_factory.ensure_namespace()

    results = real_factory.doctor.run_checks()
    ns_check = next((r for r in results if r.name == "namespace_access"), None)
    assert ns_check is not None, "namespace_access check not found"
    assert ns_check.passed, f"namespace_access failed: {ns_check.message}"
    print(f"\n  namespace_access: {ns_check.message}")


def test_03_doctor_api_reachable(real_factory: KuberokuFactory) -> None:
    """api_reachable check passes."""
    results = real_factory.doctor.run_checks()
    api_check = next((r for r in results if r.name == "api_reachable"), None)
    assert api_check is not None, "api_reachable check not found"
    assert api_check.passed, f"api_reachable failed: {api_check.message}"
    print(f"\n  api_reachable: {api_check.message}")


def test_04_fix_all_runs(real_factory: KuberokuFactory) -> None:
    """fix_all completes without error."""
    results = real_factory.doctor.fix_all(on_step=lambda s: print(f"  {s}"))
    # fix_all only returns results for checks that are fixable AND failing
    for r in results:
        status = "FIXED" if r.fixed else "SKIP"
        print(f"  [{status}] {r.check_name}: {r.message}")


def test_05_fix_all_idempotent(real_factory: KuberokuFactory) -> None:
    """Run fix_all twice — second run should produce same or fewer results."""
    results1 = real_factory.doctor.fix_all()
    results2 = real_factory.doctor.fix_all()
    # Second run should fix <= first run (since first already fixed things)
    assert len(results2) <= len(results1)
    print(f"\n  fix_all idempotent: run1={len(results1)}, run2={len(results2)}")


def test_06_all_checks_detail(real_factory: KuberokuFactory) -> None:
    """Verify all 14 checks exist with name, category, and message."""
    results = real_factory.doctor.run_checks()
    known_names = {
        "api_reachable",
        "namespace_access",
        "configmap_crud",
        "secret_crud",
        "deployment_crud",
        "service_crud",
        "network_policy_crud",
        "storage_class",
        "cni_enforcement",
        "ingress_controller",
        "ingress_crud",
        "cert_manager",
        "lb_support",
        "rbac_permissions",
    }
    found_names = {r.name for r in results}
    # All known checks should be present
    for name in known_names:
        assert name in found_names, f"Missing check: {name}"
        check = next(r for r in results if r.name == name)
        assert check.category, f"Check {name} has no category"
        assert check.message, f"Check {name} has no message"
        status = "PASS" if check.passed else "FAIL"
        print(f"  [{status}] {check.name} ({check.category}): {check.message}")


def test_07_setup_namespace_exists(real_factory: KuberokuFactory) -> None:
    """Verify the namespace was created by setup."""
    exists = real_factory.k8s.namespace_exists(real_factory.namespace)
    assert exists, f"Namespace {real_factory.namespace} should exist after setup"
    print(f"\n  Namespace {real_factory.namespace} exists")


# ── Phase 11: RBAC Doctor tests ──────────────────────────────────


def test_08_rbac_check_exists(real_factory: KuberokuFactory) -> None:
    """The rbac_permissions check is present in results."""
    results = real_factory.doctor.run_checks()
    rbac = next((r for r in results if r.name == "rbac_permissions"), None)
    assert rbac is not None, "rbac_permissions check not found"
    status = "PASS" if rbac.passed else "FAIL"
    print(f"\n  rbac_permissions: [{status}] {rbac.message}")


def test_09_rbac_report(real_factory: KuberokuFactory) -> None:
    """check_rbac_permissions returns an RbacReport with permissions tuple."""
    report = real_factory.doctor.check_rbac_permissions()
    assert isinstance(report.permissions, tuple)
    assert len(report.permissions) > 0
    # On a full-perm cluster (admin), all required should be allowed
    assert report.all_required_allowed, (
        f"Expected all required allowed on admin cluster. "
        f"Denied: {[p for p in report.permissions if p.required and not p.allowed]}"
    )
    print(f"\n  RBAC report: {len(report.permissions)} permissions checked")
    print(f"  all_required_allowed={report.all_required_allowed}")
    print(f"  all_elevated_allowed={report.all_elevated_allowed}")


def test_10_doctor_fix_all_granted(real_factory: KuberokuFactory) -> None:
    """generate_rbac_fix returns missing_count=0 on full-perm cluster."""
    fix_yaml = real_factory.doctor.generate_rbac_fix()
    # On admin cluster, nothing should be missing
    assert fix_yaml.missing_count == 0, (
        f"Expected 0 missing on admin cluster, got {fix_yaml.missing_count}"
    )
    assert fix_yaml.role_yaml == ""
    assert fix_yaml.rolebinding_yaml == ""
    print(f"\n  RBAC fix: missing_count={fix_yaml.missing_count} (all granted)")


def test_11_cli_doctor_runs(real_factory: KuberokuFactory) -> None:
    """CLI: clusters doctor → exit_code 0."""
    runner = CliRunner()
    result = runner.invoke(
        clusters_group,
        ["doctor"],
        obj={"factory": real_factory},
    )
    # May exit 1 if cert-manager missing, etc. — but shouldn't crash
    assert result.exit_code in (0, 1), f"CLI crashed: {result.output}"
    assert "check" in result.output.lower() or "[" in result.output
    print(f"\n  CLI doctor exit_code={result.exit_code}")
    print(f"  Output:\n{result.output}")


def test_12_cli_doctor_fix(real_factory: KuberokuFactory) -> None:
    """CLI: clusters doctor --fix → shows RBAC status."""
    runner = CliRunner()
    result = runner.invoke(
        clusters_group,
        ["doctor", "--fix"],
        obj={"factory": real_factory},
    )
    assert result.exit_code == 0, f"CLI failed: {result.output}"
    # On full-perm cluster, should say "already granted"
    assert "already granted" in result.output.lower() or "Missing" in result.output
    print(f"\n  CLI doctor --fix:\n{result.output}")


def test_13_total_check_count_is_14(real_factory: KuberokuFactory) -> None:
    """Verify exactly 14 checks are returned."""
    results = real_factory.doctor.run_checks()
    assert len(results) == 14, (
        f"Expected 14 checks, got {len(results)}: {[r.name for r in results]}"
    )
    print(f"\n  Total checks: {len(results)}")
