"""Contract tests: StatefulSet CRUD on fake (and real with --backend=real)."""

from __future__ import annotations

import time
from typing import Any

import pytest

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


def _make_sts(name: str = "my-sts") -> dict[str, object]:
    return {
        "apiVersion": "apps/v1",
        "kind": "StatefulSet",
        "metadata": {
            "name": name,
            "labels": {"app": "test"},
        },
        "spec": {
            "replicas": 1,
            "serviceName": name,
            "selector": {"matchLabels": {"app": "test"}},
            "template": {
                "metadata": {"labels": {"app": "test"}},
                "spec": {"containers": [{"name": "main", "image": "nginx:1"}]},
            },
        },
    }


def test_create_statefulset(k8s_client: FakeK8sClient, tmp_namespace: str) -> None:
    body = _make_sts()
    result = k8s_client.create_statefulset(tmp_namespace, body)
    assert result["metadata"]["name"] == "my-sts"
    assert "resourceVersion" in result["metadata"]


def test_get_statefulset(k8s_client: FakeK8sClient, tmp_namespace: str) -> None:
    k8s_client.create_statefulset(tmp_namespace, _make_sts())
    sts = k8s_client.get_statefulset(tmp_namespace, "my-sts")
    assert sts["metadata"]["name"] == "my-sts"


def test_get_statefulset_not_found(k8s_client: FakeK8sClient, tmp_namespace: str) -> None:
    with pytest.raises(FakeNotFoundError):
        k8s_client.get_statefulset(tmp_namespace, "nonexistent")


def test_update_statefulset(k8s_client: FakeK8sClient, tmp_namespace: str) -> None:
    k8s_client.create_statefulset(tmp_namespace, _make_sts())
    # Read-modify-write with retry: real K8s STS controller may mutate after create
    for _attempt in range(5):
        sts = k8s_client.get_statefulset(tmp_namespace, "my-sts")
        sts["spec"]["replicas"] = 3
        try:
            updated = k8s_client.update_statefulset(tmp_namespace, "my-sts", sts)
            break
        except FakeConflictError:
            time.sleep(0.5)
            continue
    else:
        pytest.fail("StatefulSet update failed after 5 retries due to conflict")
    assert updated["spec"]["replicas"] == 3


def test_delete_statefulset(k8s_client: FakeK8sClient, tmp_namespace: str) -> None:
    k8s_client.create_statefulset(tmp_namespace, _make_sts())
    k8s_client.delete_statefulset(tmp_namespace, "my-sts")

    def _get_or_none() -> dict[str, Any] | None:
        try:
            return k8s_client.get_statefulset(tmp_namespace, "my-sts")
        except FakeNotFoundError:
            return None

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


def test_list_statefulsets(k8s_client: FakeK8sClient, tmp_namespace: str) -> None:
    k8s_client.create_statefulset(tmp_namespace, _make_sts("sts-1"))
    k8s_client.create_statefulset(tmp_namespace, _make_sts("sts-2"))
    result = k8s_client.list_statefulsets(tmp_namespace)
    assert len(result) >= 2


def test_list_statefulsets_with_labels(k8s_client: FakeK8sClient, tmp_namespace: str) -> None:
    k8s_client.create_statefulset(tmp_namespace, _make_sts())
    result = k8s_client.list_statefulsets(tmp_namespace, labels={"app": "test"})
    assert len(result) == 1
    result_empty = k8s_client.list_statefulsets(tmp_namespace, labels={"app": "other"})
    assert len(result_empty) == 0
