"""Contract tests: Service CRUD.

These tests run identically on FakeK8sClient and the real K8s cluster,
proving the fake matches real K8s behavior for Service 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 _service_body(name: str, labels: dict[str, str] | None = None) -> dict[str, Any]:
    """Build a minimal valid Service dict."""
    return {
        "apiVersion": "v1",
        "kind": "Service",
        "metadata": {"name": name, "labels": labels or {}},
        "spec": {
            "selector": {"app": name},
            "ports": [{"port": 80, "targetPort": 8080, "protocol": "TCP"}],
            "type": "ClusterIP",
        },
    }


class TestServiceCRUD:
    """Service create / get / update / list / delete contract."""

    def test_create_and_get(self, k8s_client, tmp_namespace):
        body = _service_body("svc-get")
        created = k8s_client.create_service(tmp_namespace, body)
        assert created["metadata"]["name"] == "svc-get"
        assert "resourceVersion" in created["metadata"]

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

    def test_list_with_labels(self, k8s_client, tmp_namespace):
        k8s_client.create_service(tmp_namespace, _service_body("svc-a", {"env": "prod"}))
        k8s_client.create_service(tmp_namespace, _service_body("svc-b", {"env": "staging"}))

        prod = k8s_client.list_services(tmp_namespace, labels={"env": "prod"})
        assert len(prod) == 1
        assert prod[0]["metadata"]["name"] == "svc-a"

    def test_delete_then_get_raises(self, k8s_client, tmp_namespace):
        body = _service_body("svc-del")
        k8s_client.create_service(tmp_namespace, body)
        k8s_client.delete_service(tmp_namespace, "svc-del")

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

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

    def test_update_changes_spec(self, k8s_client, tmp_namespace):
        body = _service_body("svc-upd")
        k8s_client.create_service(tmp_namespace, body)

        # Read-modify-write with retry: real K8s endpoint controller may mutate
        for _attempt in range(5):
            current = k8s_client.get_service(tmp_namespace, "svc-upd")
            updated_body = _service_body("svc-upd")
            updated_body["metadata"]["resourceVersion"] = current["metadata"]["resourceVersion"]
            updated_body["spec"]["ports"] = [{"port": 443, "targetPort": 8443, "protocol": "TCP"}]
            try:
                k8s_client.update_service(tmp_namespace, "svc-upd", updated_body)
                break
            except FakeConflictError:
                continue
        else:
            pytest.fail("Service update failed after 5 retries due to conflict")

        fetched = k8s_client.get_service(tmp_namespace, "svc-upd")
        ports = fetched["spec"]["ports"]
        assert any(p["port"] == 443 for p in ports)
