"""
Tests for the mutate interval API endpoint.
"""
import pytest
import httpx
from unittest.mock import patch, AsyncMock

from decnet.config import DeckyConfig, DecnetConfig


def _decky(name: str = "decky-01") -> DeckyConfig:
    return DeckyConfig(
        name=name, ip="192.168.1.10", services=["ssh"],
        distro="debian", base_image="debian", hostname="test-host",
        build_base="debian:bookworm-slim", nmap_os="linux",
        mutate_interval=30,
    )


def _config() -> DecnetConfig:
    return DecnetConfig(
        mode="unihost", interface="eth0", subnet="192.168.1.0/24",
        gateway="192.168.1.1", deckies=[_decky()],
    )


class TestMutateInterval:
    @pytest.mark.asyncio
    async def test_unauthenticated_returns_401(self, client: httpx.AsyncClient):
        resp = await client.put(
            "/api/v1/deckies/decky-01/mutate-interval",
            json={"mutate_interval": "60m"},
        )
        assert resp.status_code == 401

    @pytest.mark.asyncio
    async def test_no_active_deployment(self, client: httpx.AsyncClient, auth_token: str):
        with patch("decnet.web.router.fleet.api_mutate_interval.repo", new_callable=AsyncMock) as mock_repo:
            mock_repo.get_state.return_value = None
            resp = await client.put(
                "/api/v1/deckies/decky-01/mutate-interval",
                headers={"Authorization": f"Bearer {auth_token}"},
                json={"mutate_interval": "60m"},
            )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_decky_not_found(self, client: httpx.AsyncClient, auth_token: str):
        config = _config()
        with patch("decnet.web.router.fleet.api_mutate_interval.repo", new_callable=AsyncMock) as mock_repo:
            mock_repo.get_state.return_value = {"config": config.model_dump(), "compose_path": "c.yml"}
            resp = await client.put(
                "/api/v1/deckies/nonexistent/mutate-interval",
                headers={"Authorization": f"Bearer {auth_token}"},
                json={"mutate_interval": "60m"},
            )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_successful_interval_update(self, client: httpx.AsyncClient, auth_token: str):
        config = _config()
        with patch("decnet.web.router.fleet.api_mutate_interval.repo", new_callable=AsyncMock) as mock_repo:
            mock_repo.get_state.return_value = {"config": config.model_dump(), "compose_path": "c.yml"}
            resp = await client.put(
                "/api/v1/deckies/decky-01/mutate-interval",
                headers={"Authorization": f"Bearer {auth_token}"},
                json={"mutate_interval": "120m"},
            )
        assert resp.status_code == 200
        assert resp.json()["message"] == "Mutation interval updated"
        mock_repo.set_state.assert_awaited_once()
        saved = mock_repo.set_state.call_args[0][1]
        saved_interval = saved["config"]["deckies"][0]["mutate_interval"]
        assert saved_interval == 120

    @pytest.mark.asyncio
    async def test_null_interval_removes_mutation(self, client: httpx.AsyncClient, auth_token: str):
        config = _config()
        with patch("decnet.web.router.fleet.api_mutate_interval.repo", new_callable=AsyncMock) as mock_repo:
            mock_repo.get_state.return_value = {"config": config.model_dump(), "compose_path": "c.yml"}
            resp = await client.put(
                "/api/v1/deckies/decky-01/mutate-interval",
                headers={"Authorization": f"Bearer {auth_token}"},
                json={"mutate_interval": None},
            )
        assert resp.status_code == 200
        mock_repo.set_state.assert_awaited_once()

    @pytest.mark.asyncio
    async def test_invalid_format_returns_422(self, client: httpx.AsyncClient, auth_token: str):
        """Seconds ('s') and raw integers are not accepted.
        Note: The API returns 400 for structural violations (wrong type) and 422 for semantic/pattern violations.
        """
        cases = [
            ("1s", 422),
            ("60", 422),
            (60, 400),
            (False, 400),
            ("1h", 422),
        ]
        for bad, expected_status in cases:
            resp = await client.put(
                "/api/v1/deckies/decky-01/mutate-interval",
                headers={"Authorization": f"Bearer {auth_token}"},
                json={"mutate_interval": bad},
            )
            assert resp.status_code == expected_status, f"Expected {expected_status} for {bad!r}, got {resp.status_code}"

    @pytest.mark.asyncio
    async def test_duration_units_stored_as_minutes(self, client: httpx.AsyncClient, auth_token: str):
        """Each unit suffix is parsed to the correct number of minutes."""
        cases = [
            ("2m", 2),
            ("1d", 1440),
            ("1M", 43200),
            ("1y", 525600),
            ("1Y", 525600),
        ]
        for duration, expected_minutes in cases:
            config = _config()
            with patch("decnet.web.router.fleet.api_mutate_interval.repo", new_callable=AsyncMock) as mock_repo:
                mock_repo.get_state.return_value = {"config": config.model_dump(), "compose_path": "c.yml"}
                resp = await client.put(
                    "/api/v1/deckies/decky-01/mutate-interval",
                    headers={"Authorization": f"Bearer {auth_token}"},
                    json={"mutate_interval": duration},
                )
            assert resp.status_code == 200, f"Expected 200 for {duration!r}"
            saved = mock_repo.set_state.call_args[0][1]
            saved_interval = saved["config"]["deckies"][0]["mutate_interval"]
            assert saved_interval == expected_minutes, f"{duration!r} → expected {expected_minutes} min, got {saved_interval}"
