"""Tests for AuthResource."""

from __future__ import annotations

from typing import Any
from unittest.mock import AsyncMock
from uuid import uuid4

import pytest

from mcpgateway_sdk.models.api_key import ApiKey
from mcpgateway_sdk.resources.auth import AuthResource


@pytest.fixture
def http() -> AsyncMock:
    """Mock HTTPClient."""
    return AsyncMock()


@pytest.fixture
def auth(http: AsyncMock) -> AuthResource:
    """AuthResource with mocked HTTP."""
    return AuthResource(http)


def _api_key_data(*, with_key: bool = False) -> dict[str, Any]:
    data: dict[str, Any] = {
        "id": str(uuid4()),
        "name": "test-key",
        "description": "A test key",
        "key_prefix": "mcpg_test",
        "environment": "live",
        "scopes": ["tools:read", "tools:execute"],
        "rate_limit_rpm": 60,
        "is_active": True,
        "created_at": "2026-01-01T00:00:00Z",
        "updated_at": "2026-01-01T00:00:00Z",
        "expires_at": None,
        "last_used_at": None,
        "last_used_ip": None,
    }
    if with_key:
        data["key"] = "mcpg_test_abc123secretkey"
    return data


def _rotate_response_data() -> dict[str, Any]:
    """Response shape for the rotate endpoint."""
    return {
        "new_key": _api_key_data(with_key=True),
        "old_key_revoked": False,
        "grace_period_ends_at": "2026-01-02T00:00:00Z",
    }


class TestEndpointsDict:
    """Tests for _endpoints class variable."""

    def test_endpoints_has_create(self) -> None:
        assert AuthResource._endpoints["create_api_key"] == (
            "POST",
            "/api/v1/users/me/api-keys",
        )

    def test_endpoints_has_list(self) -> None:
        assert AuthResource._endpoints["list_api_keys"] == (
            "GET",
            "/api/v1/users/me/api-keys",
        )

    def test_endpoints_has_get(self) -> None:
        assert AuthResource._endpoints["get_api_key"] == (
            "GET",
            "/api/v1/users/me/api-keys/{key_id}",
        )

    def test_endpoints_has_rotate(self) -> None:
        assert AuthResource._endpoints["rotate_api_key"] == (
            "POST",
            "/api/v1/users/me/api-keys/{key_id}/rotate",
        )

    def test_endpoints_has_revoke(self) -> None:
        assert AuthResource._endpoints["revoke_api_key"] == (
            "DELETE",
            "/api/v1/users/me/api-keys/{key_id}",
        )

    def test_endpoints_has_exactly_five_entries(self) -> None:
        assert len(AuthResource._endpoints) == 5


class TestCreateApiKey:
    """Tests for AuthResource.create_api_key."""

    async def test_sends_post_with_required_fields(
        self, auth: AuthResource, http: AsyncMock
    ) -> None:
        # Arrange
        http.request.return_value = _api_key_data(with_key=True)

        # Act
        await auth.create_api_key(name="test-key")

        # Assert
        call_kwargs = http.request.call_args
        assert call_kwargs.args == ("POST", "/api/v1/users/me/api-keys")
        body = call_kwargs.kwargs["json"]
        assert body["name"] == "test-key"

    async def test_sends_all_optional_fields(self, auth: AuthResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = _api_key_data(with_key=True)

        # Act
        await auth.create_api_key(
            name="prod-key",
            description="Production key",
            scopes=["tools:read"],
            expires_in_days=30,
            environment="staging",
            rate_limit_rpm=120,
        )

        # Assert
        body = http.request.call_args.kwargs["json"]
        assert body["name"] == "prod-key"
        assert body["description"] == "Production key"
        assert body["scopes"] == ["tools:read"]
        assert body["expires_in_days"] == 30
        assert body["environment"] == "staging"
        assert body["rate_limit_rpm"] == 120

    async def test_sends_expires_at_as_isoformat(self, auth: AuthResource, http: AsyncMock) -> None:
        # Arrange
        from datetime import datetime, timezone

        http.request.return_value = _api_key_data(with_key=True)
        expires = datetime(2026, 12, 31, 23, 59, 59, tzinfo=timezone.utc)

        # Act
        await auth.create_api_key(name="expiring-key", expires_at=expires)

        # Assert
        body = http.request.call_args.kwargs["json"]
        assert body["expires_at"] == "2026-12-31T23:59:59+00:00"

    async def test_returns_api_key_model(self, auth: AuthResource, http: AsyncMock) -> None:
        # Arrange
        data = _api_key_data(with_key=True)
        http.request.return_value = data

        # Act
        result = await auth.create_api_key(name="test-key")

        # Assert
        assert isinstance(result, ApiKey)
        assert result.name == "test-key"
        assert result.key == "mcpg_test_abc123secretkey"
        assert result.is_active is True


class TestListApiKeys:
    """Tests for AuthResource.list_api_keys."""

    async def test_sends_get_request(self, auth: AuthResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {"items": [], "total": 0, "page": 1, "page_size": 20}

        # Act
        await auth.list_api_keys()

        # Assert
        http.request.assert_awaited_once_with("GET", "/api/v1/users/me/api-keys")

    async def test_returns_list_of_api_keys(self, auth: AuthResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {
            "items": [_api_key_data(), _api_key_data()],
            "total": 2,
            "page": 1,
            "page_size": 20,
        }

        # Act
        result = await auth.list_api_keys()

        # Assert
        assert len(result) == 2
        assert all(isinstance(k, ApiKey) for k in result)

    async def test_empty_list(self, auth: AuthResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {"items": [], "total": 0, "page": 1, "page_size": 20}

        # Act
        result = await auth.list_api_keys()

        # Assert
        assert result == []


class TestGetApiKey:
    """Tests for AuthResource.get_api_key."""

    async def test_sends_get_with_key_id(self, auth: AuthResource, http: AsyncMock) -> None:
        # Arrange
        key_id = str(uuid4())
        http.request.return_value = _api_key_data()

        # Act
        await auth.get_api_key(key_id)

        # Assert
        http.request.assert_awaited_once_with("GET", f"/api/v1/users/me/api-keys/{key_id}")

    async def test_accepts_uuid_key_id(self, auth: AuthResource, http: AsyncMock) -> None:
        # Arrange
        key_id = uuid4()
        http.request.return_value = _api_key_data()

        # Act
        await auth.get_api_key(key_id)

        # Assert
        http.request.assert_awaited_once_with("GET", f"/api/v1/users/me/api-keys/{key_id}")

    async def test_returns_api_key_model(self, auth: AuthResource, http: AsyncMock) -> None:
        # Arrange
        data = _api_key_data()
        http.request.return_value = data

        # Act
        result = await auth.get_api_key(str(uuid4()))

        # Assert
        assert isinstance(result, ApiKey)
        assert result.name == "test-key"


class TestRotateApiKey:
    """Tests for AuthResource.rotate_api_key."""

    async def test_sends_post_with_grace_period_body(
        self, auth: AuthResource, http: AsyncMock
    ) -> None:
        # Arrange
        key_id = str(uuid4())
        http.request.return_value = _rotate_response_data()

        # Act
        await auth.rotate_api_key(key_id)

        # Assert
        http.request.assert_awaited_once_with(
            "POST",
            f"/api/v1/users/me/api-keys/{key_id}/rotate",
            json={"grace_period_hours": 24},
        )

    async def test_custom_grace_period(self, auth: AuthResource, http: AsyncMock) -> None:
        # Arrange
        key_id = str(uuid4())
        http.request.return_value = _rotate_response_data()

        # Act
        await auth.rotate_api_key(key_id, grace_period_hours=48)

        # Assert
        http.request.assert_awaited_once_with(
            "POST",
            f"/api/v1/users/me/api-keys/{key_id}/rotate",
            json={"grace_period_hours": 48},
        )

    async def test_returns_nested_dict_with_new_key(
        self, auth: AuthResource, http: AsyncMock
    ) -> None:
        # Arrange
        data = _rotate_response_data()
        http.request.return_value = data

        # Act
        result = await auth.rotate_api_key(str(uuid4()))

        # Assert
        assert isinstance(result, dict)
        assert "new_key" in result
        assert result["old_key_revoked"] is False
        assert result["grace_period_ends_at"] is not None

    async def test_new_key_contains_secret(self, auth: AuthResource, http: AsyncMock) -> None:
        # Arrange
        data = _rotate_response_data()
        http.request.return_value = data

        # Act
        result = await auth.rotate_api_key(str(uuid4()))

        # Assert
        new_key = result["new_key"]
        assert new_key["key"] == "mcpg_test_abc123secretkey"


class TestRevokeApiKey:
    """Tests for AuthResource.revoke_api_key."""

    async def test_sends_delete_request(self, auth: AuthResource, http: AsyncMock) -> None:
        # Arrange
        key_id = str(uuid4())
        http.request.return_value = None  # 204 No Content

        # Act
        await auth.revoke_api_key(key_id)

        # Assert
        http.request.assert_awaited_once_with("DELETE", f"/api/v1/users/me/api-keys/{key_id}")

    async def test_returns_none(self, auth: AuthResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = None

        # Act
        result = await auth.revoke_api_key(str(uuid4()))

        # Assert
        assert result is None
