"""Contract tests: Deployment CRUD.

These tests run identically on FakeK8sClient and the real K8s cluster,
proving the fake matches real K8s behavior for Deployment operations.
"""

from __future__ import annotations

from typing import Any

import pytest

from tests.backends.fake import FakeConflictError, FakeNotFoundError
from tests.e2e.helpers import poll_until


def _deployment_body(
    name: str,
    labels: dict[str, str] | None = None,
    extra_meta_labels: dict[str, str] | None = None,
) -> dict[str, Any]:
    """Build a minimal valid Deployment dict.

    *labels* sets selector + template labels (immutable after creation).
    *extra_meta_labels* adds labels to metadata only (safe to change on update).
    """
    selector_labels = dict(labels or {"app": name})
    meta_labels = dict(selector_labels)
    if extra_meta_labels:
        meta_labels.update(extra_meta_labels)
    return {
        "apiVersion": "apps/v1",
        "kind": "Deployment",
        "metadata": {"name": name, "labels": meta_labels},
        "spec": {
            "replicas": 1,
            "selector": {"matchLabels": dict(selector_labels)},
            "template": {
                "metadata": {"labels": dict(selector_labels)},
                "spec": {
                    "containers": [
                        {
                            "name": name,
                            "image": "busybox:latest",
                            "command": ["sleep", "3600"],
                        }
                    ]
                },
            },
        },
    }


class TestDeploymentCRUD:
    """Deployment create / get / update / list / delete contract."""

    def test_create_and_get(self, k8s_client, tmp_namespace):
        body = _deployment_body("dep-get")
        created = k8s_client.create_deployment(tmp_namespace, body)
        assert created["metadata"]["name"] == "dep-get"
        assert "resourceVersion" in created["metadata"]

        fetched = k8s_client.get_deployment(tmp_namespace, "dep-get")
        assert fetched["metadata"]["name"] == "dep-get"

    def test_create_already_exists(self, k8s_client, tmp_namespace):
        body = _deployment_body("dep-dup")
        k8s_client.create_deployment(tmp_namespace, body)
        with pytest.raises(FakeConflictError):
            k8s_client.create_deployment(tmp_namespace, body)

    def test_list_with_label_selector(self, k8s_client, tmp_namespace):
        k8s_client.create_deployment(
            tmp_namespace,
            _deployment_body("dep-a", {"team": "alpha"}),
        )
        k8s_client.create_deployment(
            tmp_namespace,
            _deployment_body("dep-b", {"team": "beta"}),
        )

        alpha = k8s_client.list_deployments(tmp_namespace, labels={"team": "alpha"})
        assert len(alpha) == 1
        assert alpha[0]["metadata"]["name"] == "dep-a"

    def test_delete_then_get_raises(self, k8s_client, tmp_namespace):
        body = _deployment_body("dep-del")
        k8s_client.create_deployment(tmp_namespace, body)
        k8s_client.delete_deployment(tmp_namespace, "dep-del")

        def _get_or_none() -> dict[str, Any] | None:
            try:
                return k8s_client.get_deployment(tmp_namespace, "dep-del")
            except FakeNotFoundError:
                return None

        poll_until(
            fn=_get_or_none,
            predicate=lambda result: result is None,
            timeout=30,
            interval=0.5,
            message="Deployment dep-del still exists after deletion",
        )

    def test_update_changes_labels(self, k8s_client, tmp_namespace):
        body = _deployment_body("dep-upd", extra_meta_labels={"version": "v1"})
        k8s_client.create_deployment(tmp_namespace, body)

        # Read-modify-write with retry: real K8s controllers may mutate
        # the deployment after creation, causing resourceVersion races.
        for _attempt in range(5):
            current = k8s_client.get_deployment(tmp_namespace, "dep-upd")
            rv = current["metadata"]["resourceVersion"]
            updated_body = _deployment_body("dep-upd", extra_meta_labels={"version": "v2"})
            updated_body["metadata"]["resourceVersion"] = rv
            try:
                updated = k8s_client.update_deployment(tmp_namespace, "dep-upd", updated_body)
                break
            except FakeConflictError:
                continue
        else:
            pytest.fail("Update failed after 5 retries due to conflict")
        assert updated["metadata"]["labels"]["version"] == "v2"
