"""End-to-end network isolation test on a real K8s cluster.

Creates two apps, deploys nginx to both, and verifies:
- Same-app connectivity works (process-allow NetworkPolicy)
- Cross-app connectivity is blocked (deny-all NetworkPolicy)

Requires:
- K8s cluster with NetworkPolicy enforcement (k3s built-in, Calico, etc.)

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

from __future__ import annotations

from typing import Any

import pytest

from kuberoku.factory import KuberokuFactory
from kuberoku.k8s.resources import safe_name
from tests.e2e.helpers import (
    cleanup_namespace_fixture,
    make_real_factory,
    poll_until,
    skip_if_no_cluster,
    wait_for_pods_ready,
)

# ── Fixtures ─────────────────────────────────────────────────────


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


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


# ── Tests ────────────────────────────────────────────────────────


def test_00_skip_if_no_cluster(real_factory: KuberokuFactory) -> None:
    """Skip the entire module if no cluster is available."""
    skip_if_no_cluster()


def test_01_create_apps(real_factory: KuberokuFactory) -> None:
    """Create two apps: alpha and bravo."""
    alpha = real_factory.apps.create("alpha", on_step=lambda s: print(f"  alpha: {s}"))
    bravo = real_factory.apps.create("bravo", on_step=lambda s: print(f"  bravo: {s}"))
    assert alpha.name == "alpha"
    assert bravo.name == "bravo"
    print(f"\n  Apps created in namespace {real_factory.namespace}")


def test_02_deploy_both(real_factory: KuberokuFactory) -> None:
    """Deploy nginx to both apps with port 80."""
    ports = [{"containerPort": 80, "protocol": "TCP"}]
    r1 = real_factory.deploy.deploy(
        "alpha",
        image="nginx:1.27-alpine",
        ports=ports,
        wait=False,
        on_step=lambda s: print(f"  alpha: {s}"),
    )
    r2 = real_factory.deploy.deploy(
        "bravo",
        image="nginx:1.27-alpine",
        ports=ports,
        wait=False,
        on_step=lambda s: print(f"  bravo: {s}"),
    )
    assert r1.version == 1
    assert r2.version == 1
    print(f"\n  alpha: v{r1.version}, bravo: v{r2.version}")


def test_03_wait_pods(real_factory: KuberokuFactory) -> None:
    """Wait for both apps to have running pods."""
    alpha_pods = wait_for_pods_ready(real_factory, "alpha", expected=1)
    bravo_pods = wait_for_pods_ready(real_factory, "bravo", expected=1)
    assert len(alpha_pods) >= 1
    assert len(bravo_pods) >= 1
    print(f"\n  alpha pod: {alpha_pods[0]['metadata']['name']}")
    print(f"  bravo pod: {bravo_pods[0]['metadata']['name']}")


def test_04_verify_network_policies(real_factory: KuberokuFactory) -> None:
    """Verify deny-all and process-allow NetworkPolicies exist for both apps."""
    for app_name in ("alpha", "bravo"):
        # Deny-all policy
        deny_name = safe_name(real_factory.prefix, "netpol-deny", app_name)
        deny_policy = real_factory.k8s.get_network_policy(real_factory.namespace, deny_name)
        assert deny_policy is not None
        # Ingress should be empty list (deny all)
        ingress = deny_policy.get("spec", {}).get("ingress", [])
        assert ingress == [] or ingress is None
        print(f"\n  {app_name} deny-all: {deny_name} (ingress={ingress})")

        # Process-allow policy
        allow_name = safe_name(real_factory.prefix, "netpol-allow", f"{app_name}-web")
        allow_policy = real_factory.k8s.get_network_policy(real_factory.namespace, allow_name)
        assert allow_policy is not None
        allow_ingress = allow_policy.get("spec", {}).get("ingress", [])
        assert len(allow_ingress) > 0
        print(f"  {app_name} process-allow: {allow_name} (ingress rules={len(allow_ingress)})")


def test_05_same_app_connectivity(real_factory: KuberokuFactory) -> None:
    """Alpha pod can reach alpha service (same-app, process-allow).

    NetworkPolicy propagation can take 1-5s after creation, so poll
    instead of single-shot to avoid flaky failures on slow clusters.
    """
    alpha_pods = wait_for_pods_ready(real_factory, "alpha", expected=1)
    pod_name = alpha_pods[0]["metadata"]["name"]
    svc_name = safe_name(real_factory.prefix, "alpha", "web")

    print(f"\n  Exec in {pod_name}: wget {svc_name}:80")
    output = poll_until(
        fn=lambda: real_factory.k8s.exec_in_pod(
            real_factory.namespace,
            pod_name,
            ["wget", "-qO-", "-T", "5", f"http://{svc_name}:80/"],
        ),
        predicate=lambda o: len(o) > 0 and ("nginx" in o.lower() or "html" in o.lower()),
        timeout=30,
        interval=2,
        message=f"Same-app wget to {svc_name}:80 never returned nginx response",
    )
    print(f"  Response length: {len(output)}")


def test_06_cross_app_blocked_alpha_to_bravo(real_factory: KuberokuFactory) -> None:
    """Alpha pod CANNOT reach bravo service (cross-app, deny-all blocks)."""
    alpha_pods = wait_for_pods_ready(real_factory, "alpha", expected=1)
    pod_name = alpha_pods[0]["metadata"]["name"]
    bravo_svc = safe_name(real_factory.prefix, "bravo", "web")

    print(f"\n  Exec in {pod_name}: nc {bravo_svc}:80 (should timeout/fail)")
    try:
        real_factory.k8s.exec_in_pod(
            real_factory.namespace,
            pod_name,
            ["sh", "-c", f"nc -w 3 {bravo_svc} 80 < /dev/null"],
        )
        # nc exit 0 = connected = NOT blocked
        pytest.fail(
            "Cross-app request succeeded! NetworkPolicy not enforcing isolation. "
            "Ensure your cluster has a NetworkPolicy controller."
        )
    except Exception as e:
        # Expected: timeout, connection refused, or non-zero exit
        print(f"  Correctly blocked: {e}")


def test_07_cross_app_blocked_bravo_to_alpha(real_factory: KuberokuFactory) -> None:
    """Bravo pod CANNOT reach alpha service (cross-app, deny-all blocks)."""
    bravo_pods = wait_for_pods_ready(real_factory, "bravo", expected=1)
    pod_name = bravo_pods[0]["metadata"]["name"]
    alpha_svc = safe_name(real_factory.prefix, "alpha", "web")

    print(f"\n  Exec in {pod_name}: nc {alpha_svc}:80 (should timeout/fail)")
    try:
        real_factory.k8s.exec_in_pod(
            real_factory.namespace,
            pod_name,
            ["sh", "-c", f"nc -w 3 {alpha_svc} 80 < /dev/null"],
        )
        pytest.fail(
            "Cross-app request succeeded! NetworkPolicy not enforcing isolation. "
            "Ensure your cluster has a NetworkPolicy controller."
        )
    except Exception as e:
        print(f"  Correctly blocked: {e}")


def test_08_destroy_apps(real_factory: KuberokuFactory) -> None:
    """Destroy both apps and verify cleanup."""
    real_factory.apps.destroy("alpha")
    real_factory.apps.destroy("bravo")

    app_list = real_factory.apps.list()
    assert not any(a.name in ("alpha", "bravo") for a in app_list)
    print("\n  Both apps destroyed successfully")
