"""E2E: NodeLifecycleController full cycle in dry-run mode."""

import asyncio

import pytest

from apollo.spoke.node_lifecycle.controller import (
    CycleResult,
    NodeLifecycleConfig,
    NodeLifecycleController,
)
from citadel_operator.k3s.node import LABEL_TERMINATE_REASON

CRD_GROUP = "citadel.dev"
CRD_VERSION = "v1alpha1"
CRD_PLURAL = "nodeterminationpolicies"

pytestmark = pytest.mark.usefixtures("cluster_reachable", "install_crds")


def _run_async(coro):
    """Run an async coroutine in a new event loop (test helper)."""
    loop = asyncio.new_event_loop()
    try:
        return loop.run_until_complete(coro)
    finally:
        loop.close()


class TestControllerDryRun:
    """Exercise the full controller cycle against the live cluster in dry-run mode."""

    def test_controller_connects_without_error(self):
        """Controller should initialize and connect to the cluster."""
        cfg = NodeLifecycleConfig(dry_run_override=True)
        controller = NodeLifecycleController(config=cfg)
        assert controller is not None

    def test_cycle_with_no_policies(self):
        """Cycle with no policies should return zeros."""
        cfg = NodeLifecycleConfig(dry_run_override=True)
        controller = NodeLifecycleController(config=cfg)

        result: CycleResult = _run_async(controller.run_evaluation_cycle())
        # If no policies exist, we get zeros
        # (there might be leftover policies, so just verify the result shape)
        assert result.policies_evaluated >= 0
        assert result.nodes_checked >= 0

    def test_dry_run_policy_labels_but_no_drain(
        self, k8s_clients, create_policy, node_name
    ):
        """A dry-run policy should label matched nodes but not drain them."""
        core = k8s_clients["core"]

        # Create a dry-run policy with a tiny age threshold
        create_policy(
            name="e2e-dryrun-policy",
            spec={
                "reason": "E2E dry-run test",
                "priority": 10,
                "dryRun": True,
                "selector": {"maxAgeHours": 0.001},
            },
        )

        cfg = NodeLifecycleConfig(dry_run_override=True)
        controller = NodeLifecycleController(config=cfg)
        result: CycleResult = _run_async(controller.run_evaluation_cycle())

        assert result.policies_evaluated >= 1
        assert result.nodes_matched >= 1
        assert result.nodes_drained == 0
        assert len(result.dry_run_only) >= 1

        # Verify the node got labeled
        node = core.read_node(node_name)
        labels = node.metadata.labels or {}
        assert LABEL_TERMINATE_REASON in labels

        # Verify node is NOT cordoned (dry-run should not drain)
        assert not node.spec.unschedulable, "Dry-run should not cordon the node"

    def test_dry_run_override_prevents_real_drain(
        self, k8s_clients, create_policy, node_name
    ):
        """dry_run_override=True should prevent drain even for non-dry-run policies."""
        # Create a NON-dry-run policy
        create_policy(
            name="e2e-override-policy",
            spec={
                "reason": "E2E override test",
                "priority": 10,
                "dryRun": False,
                "selector": {"maxAgeHours": 0.001},
            },
        )

        # But controller has dry_run_override=True
        cfg = NodeLifecycleConfig(dry_run_override=True)
        controller = NodeLifecycleController(config=cfg)
        result: CycleResult = _run_async(controller.run_evaluation_cycle())

        assert result.nodes_drained == 0
        assert len(result.dry_run_only) >= 1

    def test_policy_status_updated_after_cycle(self, k8s_clients, create_policy):
        """Policy status subresource should reflect evaluation results."""
        custom = k8s_clients["custom"]

        create_policy(
            name="e2e-status-cycle",
            spec={
                "reason": "E2E status test",
                "priority": 10,
                "dryRun": True,
                "selector": {"maxAgeHours": 0.001},
            },
        )

        cfg = NodeLifecycleConfig(dry_run_override=True)
        controller = NodeLifecycleController(config=cfg)
        _run_async(controller.run_evaluation_cycle())

        # Check status was updated
        fetched = custom.get_cluster_custom_object(
            group=CRD_GROUP,
            version=CRD_VERSION,
            plural=CRD_PLURAL,
            name="e2e-status-cycle",
        )
        status = fetched.get("status", {})
        assert "lastEvaluated" in status
        assert status.get("nodesMatched", 0) >= 1
