"""Tests for module endpoints."""

from __future__ import annotations

from typing import Any
from unittest.mock import MagicMock

from fastapi.testclient import TestClient

from ilum.core.kubernetes import PodStatus


class TestListModules:
    def test_list_all_modules(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        mock_api_manager.fetch_computed_values.return_value = {}
        resp = api_client.get("/api/v1/modules")
        assert resp.status_code == 200
        data = resp.json()
        assert len(data) > 0
        # Every module has required fields
        for m in data:
            assert "name" in m
            assert "description" in m
            assert "category" in m

    def test_filter_by_category(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        mock_api_manager.fetch_computed_values.return_value = {}
        resp = api_client.get("/api/v1/modules?category=notebook")
        assert resp.status_code == 200
        data = resp.json()
        assert all(m["category"] == "notebook" for m in data)
        names = {m["name"] for m in data}
        assert "jupyter" in names

    def test_filter_by_unknown_category(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.get("/api/v1/modules?category=nonexistent")
        assert resp.status_code == 400

    def test_filter_by_enabled(
        self,
        api_client: TestClient,
        mock_api_manager: MagicMock,
        sample_values: dict[str, Any],
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = sample_values
        resp = api_client.get("/api/v1/modules?enabled=true")
        assert resp.status_code == 200
        data = resp.json()
        assert all(m["enabled"] is True for m in data)

    def test_filter_by_disabled(
        self,
        api_client: TestClient,
        mock_api_manager: MagicMock,
        sample_values: dict[str, Any],
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = sample_values
        resp = api_client.get("/api/v1/modules?enabled=false")
        assert resp.status_code == 200
        data = resp.json()
        assert all(m["enabled"] is False for m in data)

    def test_list_modules_includes_status_field(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = {
            "ilum-jupyter": {"enabled": True},
        }
        resp = api_client.get("/api/v1/modules")
        assert resp.status_code == 200
        data = resp.json()
        jupyter = next(m for m in data if m["name"] == "jupyter")
        assert jupyter["status"] == "enabled"
        kafka = next(m for m in data if m["name"] == "kafka")
        assert kafka["status"] == "disabled"

    def test_list_modules_includes_console_path(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = {}
        resp = api_client.get("/api/v1/modules")
        assert resp.status_code == 200
        data = resp.json()
        jupyter = next(m for m in data if m["name"] == "jupyter")
        assert jupyter["console_path"] == "/external/jupyter/"
        # Modules without web UI have empty console_path
        mongodb = next(m for m in data if m["name"] == "mongodb")
        assert mongodb["console_path"] == ""

    def test_list_modules_includes_version_fields(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = {
            "ilum-jupyter": {
                "enabled": True,
                "image": {"repository": "ilum/jupyter", "tag": "6.7.0"},
            },
        }
        resp = api_client.get("/api/v1/modules")
        assert resp.status_code == 200
        data = resp.json()
        jupyter = next(m for m in data if m["name"] == "jupyter")
        assert jupyter["version"] == "6.7.0"
        assert jupyter["chart_version"] == "6.7.0"  # from mock_api_manager fixture

    def test_status_shows_enabling_during_active_operation(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = {}
        # Trigger an enable operation (now creates a Job)
        resp = api_client.post("/api/v1/modules/jupyter/enable")
        assert resp.status_code == 202

        # Immediately check — status should be "enabling"
        resp = api_client.get("/api/v1/modules")
        assert resp.status_code == 200
        data = resp.json()
        jupyter = next(m for m in data if m["name"] == "jupyter")
        # Status should be enabling (operation is running via Job)
        assert jupyter["status"] in ("enabling", "enabled", "disabled")


class TestGetModule:
    def test_get_known_module(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        mock_api_manager.fetch_computed_values.return_value = {"ilum-jupyter": {"enabled": True}}
        mock_api_manager.k8s.list_pods_by_label.return_value = [
            PodStatus(
                name="ilum-jupyter-0",
                namespace="default",
                phase="Running",
                ready=True,
                restart_count=0,
            ),
        ]
        resp = api_client.get("/api/v1/modules/jupyter")
        assert resp.status_code == 200
        data = resp.json()
        assert data["name"] == "jupyter"
        assert data["category"] == "notebook"
        assert data["enabled"] is True
        assert len(data["pods"]) == 1
        assert data["pods"][0]["ready"] is True

    def test_get_unknown_module(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        resp = api_client.get("/api/v1/modules/nonexistent")
        assert resp.status_code == 400
        assert "ILUM-040" in resp.json()["error_code"]

    def test_module_detail_includes_dependencies(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = {}
        mock_api_manager.k8s.list_pods_by_label.return_value = []
        resp = api_client.get("/api/v1/modules/sql")
        assert resp.status_code == 200
        data = resp.json()
        assert "postgresql" in data["requires"]
        assert "core" in data["requires"]

    def test_module_detail_includes_status_and_console_path(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = {"ilum-jupyter": {"enabled": True}}
        mock_api_manager.k8s.list_pods_by_label.return_value = [
            PodStatus(
                name="ilum-jupyter-0",
                namespace="default",
                phase="Running",
                ready=True,
                restart_count=0,
            ),
        ]
        resp = api_client.get("/api/v1/modules/jupyter")
        assert resp.status_code == 200
        data = resp.json()
        assert data["status"] == "enabled"
        assert data["console_path"] == "/external/jupyter/"

    def test_module_detail_includes_version_fields(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = {
            "ilum-jupyter": {
                "enabled": True,
                "image": {"repository": "ilum/jupyter", "tag": "6.7.0"},
            },
        }
        mock_api_manager.k8s.list_pods_by_label.return_value = []
        resp = api_client.get("/api/v1/modules/jupyter")
        assert resp.status_code == 200
        data = resp.json()
        assert data["version"] == "6.7.0"
        assert data["chart_version"] == "6.7.0"

    def test_module_status_error_when_pods_not_ready(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = {"ilum-jupyter": {"enabled": True}}
        mock_api_manager.k8s.list_pods_by_label.return_value = [
            PodStatus(
                name="ilum-jupyter-0",
                namespace="default",
                phase="Pending",
                ready=False,
                restart_count=5,
            ),
        ]
        resp = api_client.get("/api/v1/modules/jupyter")
        assert resp.status_code == 200
        data = resp.json()
        assert data["status"] == "error"

    def test_module_status_disabled_when_not_enabled(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = {}
        mock_api_manager.k8s.list_pods_by_label.return_value = []
        resp = api_client.get("/api/v1/modules/jupyter")
        assert resp.status_code == 200
        data = resp.json()
        assert data["status"] == "disabled"

    def test_succeeded_job_pods_excluded_from_detail(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        """Completed Job pods (Succeeded/Failed) should be filtered out of the pods list.

        Airflow creates init Job pods (migrations, create-user) that finish
        with phase='Succeeded'.  These should not appear in the pods array
        and should not affect the module status.
        """
        mock_api_manager.fetch_computed_values.return_value = {"airflow": {"enabled": True}}
        mock_api_manager.k8s.list_pods_by_label.return_value = [
            PodStatus(
                name="airflow-api-server-0",
                namespace="default",
                phase="Running",
                ready=True,
                restart_count=0,
            ),
            PodStatus(
                name="airflow-scheduler-0",
                namespace="default",
                phase="Running",
                ready=True,
                restart_count=0,
            ),
            PodStatus(
                name="airflow-run-migrations-abc",
                namespace="default",
                phase="Succeeded",
                ready=False,
                restart_count=0,
            ),
            PodStatus(
                name="airflow-create-user-xyz",
                namespace="default",
                phase="Succeeded",
                ready=False,
                restart_count=0,
            ),
        ]
        resp = api_client.get("/api/v1/modules/airflow")
        assert resp.status_code == 200
        data = resp.json()
        # Only Running pods should be in the list
        assert len(data["pods"]) == 2
        assert all(p["phase"] == "Running" for p in data["pods"])
        # Status should be "enabled" (not "error")
        assert data["status"] == "enabled"


class TestEnableModule:
    def test_enable_returns_202(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        resp = api_client.post("/api/v1/modules/jupyter/enable")
        assert resp.status_code == 202
        data = resp.json()
        assert "id" in data
        assert data["status"] == "running"

    def test_enable_creates_job(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        resp = api_client.post("/api/v1/modules/jupyter/enable")
        assert resp.status_code == 202
        mock_api_manager.k8s.create_job.assert_called_once()

    def test_enable_unknown_module_returns_400(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.post("/api/v1/modules/nonexistent/enable")
        assert resp.status_code == 400

    def test_enable_with_set_flags(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.post(
            "/api/v1/modules/jupyter/enable",
            json={"set_flags": ["ilum-jupyter.resources.requests.memory=4Gi"]},
        )
        assert resp.status_code == 202
        data = resp.json()
        assert "id" in data
        assert data["status"] == "running"

    def test_enable_with_empty_body(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.post("/api/v1/modules/jupyter/enable", json={})
        assert resp.status_code == 202

    def test_enable_without_body(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        resp = api_client.post("/api/v1/modules/jupyter/enable")
        assert resp.status_code == 202

    def test_enable_calls_ensure_crds(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.post("/api/v1/modules/jupyter/enable")
        assert resp.status_code == 202
        mock_api_manager.ensure_module_crds.assert_called_once_with(["jupyter"])

    def test_enable_calls_plan_enable(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.post("/api/v1/modules/jupyter/enable")
        assert resp.status_code == 202
        mock_api_manager.plan_enable.assert_called_once()

    def test_enable_operation_has_job_name(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.post("/api/v1/modules/jupyter/enable")
        assert resp.status_code == 202
        op_id = resp.json()["id"]
        resp = api_client.get(f"/api/v1/operations/{op_id}")
        assert resp.status_code == 200
        data = resp.json()
        assert data["job_name"] == f"ilum-helm-{op_id}"

    def test_enable_concurrent_rejected(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        """Second enable while first is running should return 409."""
        resp1 = api_client.post("/api/v1/modules/jupyter/enable")
        assert resp1.status_code == 202
        # Second attempt should be rejected (in-memory store has active operation)
        resp2 = api_client.post("/api/v1/modules/airflow/enable")
        assert resp2.status_code == 409


class TestEnableAfterFailedJob:
    def test_enable_unblocks_after_failed_job(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        """Full lifecycle: enable -> Job fails -> next enable succeeds (not 409)."""
        # 1. First enable — creates a Job, returns 202
        resp = api_client.post("/api/v1/modules/jupyter/enable")
        assert resp.status_code == 202
        op_id = resp.json()["id"]

        # 2. Simulate the backing Job having failed
        failed_job = MagicMock()
        cond = MagicMock()
        cond.type = "Failed"
        cond.status = "True"
        cond.reason = "BackoffLimitExceeded"
        cond.message = "Job has reached the specified backoff limit"
        failed_job.status.conditions = [cond]
        failed_job.status.active = None
        failed_job.status.succeeded = None
        failed_job.status.failed = 1
        mock_api_manager.k8s.get_job.return_value = failed_job
        # No other active Jobs in K8s
        mock_api_manager.k8s.list_jobs_by_label.return_value = []

        # 3. Second enable — the concurrency guard should reconcile
        #    the stale "running" op and allow the new one through
        resp2 = api_client.post("/api/v1/modules/airflow/enable")
        assert resp2.status_code == 202, f"Expected 202, got {resp2.status_code}: {resp2.json()}"

        # 4. Verify the original operation was updated to failed
        resp_op = api_client.get(f"/api/v1/operations/{op_id}")
        assert resp_op.status_code == 200
        assert resp_op.json()["status"] == "failed"


class TestDisableModule:
    def test_disable_returns_202(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        mock_api_manager.fetch_computed_values.return_value = {"ilum-jupyter": {"enabled": True}}
        resp = api_client.post("/api/v1/modules/jupyter/disable")
        assert resp.status_code == 202
        data = resp.json()
        assert "id" in data
        assert data["status"] == "running"

    def test_disable_creates_job(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        mock_api_manager.fetch_computed_values.return_value = {"ilum-jupyter": {"enabled": True}}
        resp = api_client.post("/api/v1/modules/jupyter/disable")
        assert resp.status_code == 202
        mock_api_manager.k8s.create_job.assert_called_once()

    def test_disable_required_module_returns_400(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.post("/api/v1/modules/core/disable")
        assert resp.status_code == 400

    def test_disable_unknown_module_returns_400(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.post("/api/v1/modules/nonexistent/disable")
        assert resp.status_code == 400


class TestModuleValues:
    def test_get_module_values(self, api_client: TestClient, mock_api_manager: MagicMock) -> None:
        mock_api_manager.fetch_computed_values.return_value = {
            "ilum-jupyter": {
                "enabled": True,
                "replicaCount": 1,
                "image": {"repository": "ilum/jupyter", "tag": "latest"},
                "resources": {
                    "requests": {"memory": "2Gi", "cpu": "1"},
                    "limits": {"memory": "4Gi", "cpu": "2"},
                },
            },
        }
        resp = api_client.get("/api/v1/modules/jupyter/values")
        assert resp.status_code == 200
        data = resp.json()
        assert data["values_key"] == "ilum-jupyter"
        assert data["values"]["enabled"] is True
        assert data["values"]["replicaCount"] == 1
        assert data["values"]["resources"]["requests"]["memory"] == "2Gi"

    def test_get_module_values_empty_when_not_in_release(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = {}
        resp = api_client.get("/api/v1/modules/jupyter/values")
        assert resp.status_code == 200
        data = resp.json()
        assert data["values_key"] == "ilum-jupyter"
        assert data["values"] == {}

    def test_get_module_values_unknown_module(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.get("/api/v1/modules/nonexistent/values")
        assert resp.status_code == 400


class TestModuleConfigUpdate:
    def test_config_update_returns_202(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.put(
            "/api/v1/modules/jupyter/config",
            json={"values": {"replicaCount": 2}},
        )
        assert resp.status_code == 202
        data = resp.json()
        assert "id" in data
        assert data["status"] == "running"

    def test_config_update_creates_job(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.put(
            "/api/v1/modules/jupyter/config",
            json={"values": {"replicaCount": 2}},
        )
        assert resp.status_code == 202
        mock_api_manager.k8s.create_job.assert_called_once()

    def test_config_update_empty_values_returns_400(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.put(
            "/api/v1/modules/jupyter/config",
            json={"values": {}},
        )
        assert resp.status_code == 400


class TestConcurrencyBeforeRecovery:
    """B3: Concurrency guard must run BEFORE stuck-release recovery.

    During an active helm upgrade the release is in ``pending-upgrade``.
    If ``ensure_release_not_stuck`` runs first it will rollback the
    active operation.  The concurrency guard should return 409 instead.
    """

    def test_enable_returns_409_without_rollback(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        # First enable creates an active operation
        resp1 = api_client.post("/api/v1/modules/jupyter/enable")
        assert resp1.status_code == 202

        # Simulate release appearing stuck (concurrent helm upgrade)
        mock_api_manager.is_stuck.return_value = True

        # Second enable must get 409 from concurrency guard
        resp2 = api_client.post("/api/v1/modules/airflow/enable")
        assert resp2.status_code == 409

        # ensure_release_not_stuck should NOT have called rollback
        mock_api_manager.helm.rollback.assert_not_called()

    def test_disable_returns_409_without_rollback(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = {"ilum-jupyter": {"enabled": True}}
        resp1 = api_client.post("/api/v1/modules/jupyter/enable")
        assert resp1.status_code == 202

        mock_api_manager.is_stuck.return_value = True

        resp2 = api_client.post("/api/v1/modules/jupyter/disable")
        assert resp2.status_code == 409
        mock_api_manager.helm.rollback.assert_not_called()

    def test_config_update_returns_409_without_rollback(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp1 = api_client.post("/api/v1/modules/jupyter/enable")
        assert resp1.status_code == 202

        mock_api_manager.is_stuck.return_value = True

        resp2 = api_client.put(
            "/api/v1/modules/jupyter/config",
            json={"values": {"replicaCount": 2}},
        )
        assert resp2.status_code == 409
        mock_api_manager.helm.rollback.assert_not_called()


class TestStreamlitPodLabel:
    """B4: Streamlit pod_label must match the actual pod label."""

    def test_streamlit_pod_label_matches_chart(self) -> None:
        from ilum.core.modules import ModuleResolver

        resolver = ModuleResolver()
        mod = resolver.get("streamlit")
        assert mod.pod_label == "app.kubernetes.io/name=streamlit"


class TestDetermineStatusAwaitingReadiness:
    """E-B3: _determine_status should check awaiting_readiness operations."""

    def test_status_shows_enabling_during_awaiting_readiness(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        """Module should show 'enabling' while operation is awaiting_readiness."""
        import ilum.api.operations as ops_mod

        mock_api_manager.fetch_computed_values.return_value = {"ilum-jupyter": {"enabled": True}}
        mock_api_manager.k8s.list_pods_by_label.return_value = [
            PodStatus(
                name="ilum-jupyter-0",
                namespace="default",
                phase="Pending",
                ready=False,
                restart_count=0,
            ),
        ]

        # Manually create an operation in awaiting_readiness state
        store = ops_mod._store
        assert store is not None
        op_id = store.create(operation="enable", modules=["jupyter"])
        op = store.get(op_id)
        assert op is not None
        op.status = "awaiting_readiness"

        resp = api_client.get("/api/v1/modules/jupyter")
        assert resp.status_code == 200
        data = resp.json()
        assert data["status"] == "enabling"


class TestConfigUpdateNullValues:
    """E-B6: Null values should become 'null' not 'None' in set_flags."""

    def test_null_value_uses_helm_null(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.put(
            "/api/v1/modules/jupyter/config",
            json={"values": {"someKey": None}},
        )
        assert resp.status_code == 202
        # Verify plan_upgrade was called with "=null" not "=None"
        call_args = mock_api_manager.plan_upgrade.call_args
        assert call_args is not None
        flags = call_args.kwargs.get("set_flags", call_args[1].get("set_flags", []))
        assert any("ilum-jupyter.someKey=null" in f for f in flags)
        assert not any("=None" in f for f in flags)

    def test_non_null_value_uses_python_repr(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        resp = api_client.put(
            "/api/v1/modules/jupyter/config",
            json={"values": {"replicaCount": 2}},
        )
        assert resp.status_code == 202
        call_args = mock_api_manager.plan_upgrade.call_args
        assert call_args is not None
        flags = call_args.kwargs.get("set_flags", call_args[1].get("set_flags", []))
        assert any("ilum-jupyter.replicaCount=2" in f for f in flags)


class TestDisableDedupResolver:
    """E-B2: Disable should only call resolver.get once."""

    def test_disable_calls_resolver_get_once(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = {"ilum-jupyter": {"enabled": True}}
        mock_api_manager.resolver.get = MagicMock(wraps=mock_api_manager.resolver.get)
        resp = api_client.post("/api/v1/modules/jupyter/disable")
        assert resp.status_code == 202
        # Should be called exactly once (the duplicate was removed)
        assert mock_api_manager.resolver.get.call_count == 1


class TestLogsPreviousContainerFallback:
    """E4.9: previous=true should return empty lines when no previous container."""

    def test_previous_true_no_container_returns_empty(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.k8s.list_pods_by_label.return_value = [
            PodStatus(
                name="ilum-core-0",
                namespace="default",
                phase="Running",
                ready=True,
                restart_count=0,
            ),
        ]
        mock_api_manager.k8s.read_pod_log.side_effect = Exception(
            "previous terminated container not found"
        )
        resp = api_client.get("/api/v1/modules/core/logs?previous=true")
        assert resp.status_code == 200
        data = resp.json()
        assert data["lines"] == []
        assert data["pod_name"] == "ilum-core-0"

    def test_previous_false_error_still_raises(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.k8s.list_pods_by_label.return_value = [
            PodStatus(
                name="ilum-core-0",
                namespace="default",
                phase="Running",
                ready=True,
                restart_count=0,
            ),
        ]
        mock_api_manager.k8s.read_pod_log.side_effect = Exception("some error")
        resp = api_client.get("/api/v1/modules/core/logs?previous=false")
        assert resp.status_code == 500


class TestEnableReturnsWarnings:
    """API enable endpoint should return conflict warnings and auto-resolved deps."""

    def test_enable_returns_warnings_field(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = {}
        resp = api_client.post("/api/v1/modules/jupyter/enable")
        assert resp.status_code == 202
        data = resp.json()
        assert "warnings" in data
        assert isinstance(data["warnings"], list)

    def test_enable_returns_auto_resolved_field(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        mock_api_manager.fetch_computed_values.return_value = {}
        resp = api_client.post("/api/v1/modules/jupyter/enable")
        assert resp.status_code == 202
        data = resp.json()
        assert "auto_resolved" in data
        assert isinstance(data["auto_resolved"], list)

    def test_enable_returns_auto_resolved_deps(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        """Enabling trino should auto-resolve hive-metastore, sql, etc."""
        mock_api_manager.fetch_computed_values.return_value = {}
        resp = api_client.post("/api/v1/modules/trino/enable")
        assert resp.status_code == 202
        data = resp.json()
        assert "hive-metastore" in data["auto_resolved"]
        assert "sql" in data["auto_resolved"]

    def test_enable_returns_conflict_warnings(
        self,
        api_client: TestClient,
        mock_api_manager: MagicMock,
    ) -> None:
        """Enabling unity-catalog when hive-metastore is active returns a warning."""
        mock_api_manager.fetch_computed_values.return_value = {
            "ilum-hive-metastore": {"enabled": True},
            "ilum-core": {"metastore": {"enabled": True, "type": "hive"}, "enabled": True},
            "postgresql": {"enabled": True},
        }
        resp = api_client.post("/api/v1/modules/unity-catalog/enable")
        assert resp.status_code == 202
        data = resp.json()
        assert any("conflict" in w.lower() for w in data["warnings"])


class TestDisableReturnsWarnings:
    """API disable endpoint should warn about dependent modules."""

    def test_disable_warns_about_dependents(
        self, api_client: TestClient, mock_api_manager: MagicMock
    ) -> None:
        """Disabling postgresql when airflow depends on it should warn."""
        mock_api_manager.fetch_computed_values.return_value = {
            "postgresql": {"enabled": True},
            "airflow": {"enabled": True},
        }
        resp = api_client.post("/api/v1/modules/postgresql/disable")
        assert resp.status_code == 202
        data = resp.json()
        assert any("airflow" in w for w in data["warnings"])
