"""Tests for values endpoints."""

from __future__ import annotations

from typing import Any
from unittest.mock import MagicMock

from fastapi.testclient import TestClient

from ilum.core.safety import DriftResult, ValuesDiff


class TestGetValues:
    def test_returns_live_values(
        self,
        api_client: TestClient,
        mock_api_manager: MagicMock,
        sample_values: dict[str, Any],
    ) -> None:
        mock_api_manager.fetch_live_values.return_value = sample_values
        resp = api_client.get("/api/v1/values")
        assert resp.status_code == 200
        data = resp.json()
        assert data["ilum-core"]["enabled"] is True

    def test_returns_empty_when_no_values(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_live_values.return_value = {}
        resp = api_client.get("/api/v1/values")
        assert resp.status_code == 200
        assert resp.json() == {}


class TestValuesDiff:
    def test_no_drift(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        mock_api_manager.check_drift.return_value = DriftResult(
            has_drift=False,
            snapshot_exists=True,
            diff=ValuesDiff(),
            live_values={},
        )
        resp = api_client.get("/api/v1/values/diff")
        assert resp.status_code == 200
        data = resp.json()
        assert data["has_drift"] is False
        assert data["changes"] == []

    def test_with_drift(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        mock_api_manager.check_drift.return_value = DriftResult(
            has_drift=True,
            snapshot_exists=True,
            diff=ValuesDiff(
                added={"kafka.enabled": True},
                removed={"old.key": "val"},
                changed={"minio.enabled": (True, False)},
            ),
            live_values={},
        )
        resp = api_client.get("/api/v1/values/diff")
        assert resp.status_code == 200
        data = resp.json()
        assert data["has_drift"] is True
        assert len(data["changes"]) == 3
        types = {c["change_type"] for c in data["changes"]}
        assert types == {"added", "removed", "changed"}

    def test_no_snapshot(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        mock_api_manager.check_drift.return_value = DriftResult(
            has_drift=False,
            snapshot_exists=False,
            diff=None,
            live_values={},
        )
        resp = api_client.get("/api/v1/values/diff")
        assert resp.status_code == 200
        data = resp.json()
        assert data["snapshot_exists"] is False


class TestUpdateValues:
    def test_update_returns_202(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        resp = api_client.put(
            "/api/v1/values",
            json={"set_flags": ["kafka.enabled=true"]},
        )
        assert resp.status_code == 202
        data = resp.json()
        assert "id" in data
        assert data["status"] == "running"

    def test_update_creates_job(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        resp = api_client.put(
            "/api/v1/values",
            json={"set_flags": ["kafka.enabled=true"]},
        )
        assert resp.status_code == 202
        mock_api_manager.k8s.create_job.assert_called_once()

    def test_update_concurrent_rejected(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        """Second update while first is running should return 409."""
        resp1 = api_client.put(
            "/api/v1/values",
            json={"set_flags": ["kafka.enabled=true"]},
        )
        assert resp1.status_code == 202
        resp2 = api_client.put(
            "/api/v1/values",
            json={"set_flags": ["minio.enabled=true"]},
        )
        assert resp2.status_code == 409

    def test_update_empty_set_flags_rejected(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        """G1: Empty set_flags should return 400."""
        resp = api_client.put(
            "/api/v1/values",
            json={"set_flags": []},
        )
        assert resp.status_code == 400
        assert "No set_flags" in resp.json()["detail"]

    def test_update_missing_set_flags_rejected(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        """G1: Omitting set_flags (defaults to []) should return 400."""
        resp = api_client.put(
            "/api/v1/values",
            json={},
        )
        assert resp.status_code == 400

    def test_values_update_returns_409_without_rollback(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        """B3: Concurrency guard must fire before stuck-release recovery."""
        resp1 = api_client.put(
            "/api/v1/values",
            json={"set_flags": ["kafka.enabled=true"]},
        )
        assert resp1.status_code == 202

        mock_api_manager.is_stuck.return_value = True

        resp2 = api_client.put(
            "/api/v1/values",
            json={"set_flags": ["minio.enabled=true"]},
        )
        assert resp2.status_code == 409
        mock_api_manager.helm.rollback.assert_not_called()
