"""Tests for DNS record management in DnsManager.

Tests list, get, create, update, delete DNS records.
"""

from unittest.mock import AsyncMock, MagicMock

import pytest

from unifi_core.exceptions import UniFiNotFoundError


class TestDnsManager:
    """Tests for DnsManager methods."""

    @pytest.fixture
    def mock_connection(self):
        """Create a mock ConnectionManager."""
        conn = MagicMock()
        conn.site = "default"
        conn.request = AsyncMock()
        conn.get_cached = MagicMock(return_value=None)
        conn._update_cache = MagicMock()
        conn._invalidate_cache = MagicMock()
        conn.ensure_connected = AsyncMock(return_value=True)
        return conn

    @pytest.fixture
    def dns_manager(self, mock_connection):
        """Create a DnsManager with mocked connection."""
        from unifi_core.network.managers.dns_manager import DnsManager

        return DnsManager(mock_connection)

    # ---- List DNS Records ----

    @pytest.mark.asyncio
    async def test_list_dns_records_returns_list(self, dns_manager, mock_connection):
        """Test list_dns_records returns list of record dicts."""
        records = [
            {"_id": "r1", "key": "host.example.com", "value": "10.0.0.1", "record_type": "A"},
            {"_id": "r2", "key": "alias.example.com", "value": "host.example.com", "record_type": "CNAME"},
        ]
        mock_connection.request.return_value = records

        result = await dns_manager.list_dns_records()

        assert len(result) == 2
        assert result[0]["key"] == "host.example.com"

    @pytest.mark.asyncio
    async def test_list_dns_records_uses_cache(self, dns_manager, mock_connection):
        """Test list_dns_records uses cache when available."""
        cached = [{"_id": "r1", "key": "cached.example.com"}]
        mock_connection.get_cached.return_value = cached

        result = await dns_manager.list_dns_records()

        assert result == cached
        mock_connection.request.assert_not_called()

    @pytest.mark.asyncio
    async def test_list_dns_records_handles_error(self, dns_manager, mock_connection):
        """Test list_dns_records returns empty list on error."""
        mock_connection.request.side_effect = Exception("Connection failed")

        with pytest.raises(Exception):
            await dns_manager.list_dns_records()

    # ---- Get DNS Record ----

    @pytest.mark.asyncio
    async def test_get_dns_record_found(self, dns_manager, mock_connection):
        """Test get_dns_record returns record when found."""
        records = [
            {"_id": "r1", "key": "host.example.com", "value": "10.0.0.1"},
            {"_id": "r2", "key": "other.example.com", "value": "10.0.0.2"},
        ]
        mock_connection.request.return_value = records

        result = await dns_manager.get_dns_record("r1")

        assert result is not None
        assert result["key"] == "host.example.com"

    @pytest.mark.asyncio
    async def test_get_dns_record_not_found(self, dns_manager, mock_connection):
        """Test get_dns_record raises UniFiNotFoundError when not found."""
        mock_connection.request.return_value = [{"_id": "r1"}]

        with pytest.raises(UniFiNotFoundError):
            await dns_manager.get_dns_record("nonexistent")

    # ---- Create DNS Record ----

    @pytest.mark.asyncio
    async def test_create_dns_record_success(self, dns_manager, mock_connection):
        """Test create_dns_record returns created record."""
        created = {"_id": "r_new", "key": "new.example.com", "value": "10.0.0.99", "record_type": "A"}
        mock_connection.request.return_value = created

        result = await dns_manager.create_dns_record(
            {"key": "new.example.com", "value": "10.0.0.99", "record_type": "A"}
        )

        assert result is not None
        assert result["_id"] == "r_new"
        mock_connection._invalidate_cache.assert_called_once()

    @pytest.mark.asyncio
    async def test_create_dns_record_handles_error(self, dns_manager, mock_connection):
        """Test create_dns_record returns None on error."""
        mock_connection.request.side_effect = Exception("Failed")

        with pytest.raises(Exception):
            await dns_manager.create_dns_record({"key": "fail.example.com"})

    # ---- Update DNS Record ----

    @pytest.mark.asyncio
    async def test_update_dns_record_success(self, dns_manager, mock_connection):
        """Test update_dns_record uses fetch-merge-put and returns merged record."""
        existing = [{"_id": "r1", "key": "host.example.com", "value": "10.0.0.1", "record_type": "A"}]
        # First call: list (for get_dns_record), second call: PUT
        mock_connection.request.side_effect = [existing, {}]

        result = await dns_manager.update_dns_record("r1", {"value": "10.0.0.2"})

        assert result["_id"] == "r1"
        assert result["value"] == "10.0.0.2"
        assert result["key"] == "host.example.com"
        # Verify PUT was called with merged data
        put_call = mock_connection.request.call_args_list[1]
        put_req = put_call[0][0]
        assert put_req.method == "put"
        assert "r1" in put_req.path

    @pytest.mark.asyncio
    async def test_update_dns_record_not_found(self, dns_manager, mock_connection):
        """Test update_dns_record raises UniFiNotFoundError when record missing."""
        mock_connection.request.return_value = []

        with pytest.raises(UniFiNotFoundError):
            await dns_manager.update_dns_record("nonexistent", {"value": "10.0.0.2"})

    @pytest.mark.asyncio
    async def test_update_dns_record_handles_error(self, dns_manager, mock_connection):
        """Test update_dns_record returns False on error."""
        mock_connection.request.side_effect = Exception("Failed")

        with pytest.raises(Exception):
            await dns_manager.update_dns_record("r1", {"value": "10.0.0.2"})

    # ---- Delete DNS Record ----

    @pytest.mark.asyncio
    async def test_delete_dns_record_success(self, dns_manager, mock_connection):
        """Test delete_dns_record sends DELETE request."""
        mock_connection.request.return_value = {}

        result = await dns_manager.delete_dns_record("r1")

        assert result is True
        call_args = mock_connection.request.call_args
        api_req = call_args[0][0]
        assert api_req.method == "delete"
        assert "r1" in api_req.path
        mock_connection._invalidate_cache.assert_called_once()

    @pytest.mark.asyncio
    async def test_delete_dns_record_handles_error(self, dns_manager, mock_connection):
        """Test delete_dns_record returns False on error."""
        mock_connection.request.side_effect = Exception("Failed")

        with pytest.raises(Exception):
            await dns_manager.delete_dns_record("r1")

    # ---- API Path Verification ----

    @pytest.mark.asyncio
    async def test_list_uses_correct_path(self, dns_manager, mock_connection):
        """Test list_dns_records uses v2 /static-dns endpoint."""
        mock_connection.request.return_value = []

        await dns_manager.list_dns_records()

        call_args = mock_connection.request.call_args
        api_req = call_args[0][0]
        assert api_req.path == "/static-dns"

    @pytest.mark.asyncio
    async def test_create_uses_post(self, dns_manager, mock_connection):
        """Test create_dns_record uses POST."""
        mock_connection.request.return_value = {"_id": "new"}

        await dns_manager.create_dns_record({"key": "test", "value": "10.0.0.1", "record_type": "A"})

        call_args = mock_connection.request.call_args
        api_req = call_args[0][0]
        assert api_req.method == "post"
        assert api_req.path == "/static-dns"
