import uuid
from datetime import datetime
from unittest.mock import Mock
from zoneinfo import ZoneInfo

import pytest
from psycopg import Connection
from pydantic import SecretStr
from pytest_mock import MockFixture
from src.__types.errors import ClientError
from src.__types.index import Client
from src.domain.entities.client_api_key import ClientApiKey, ClientApiKeyAlgorithm, ClientApiKeyStatus
from src.domain.services.client_api_key_service import ClientApiKeyService
from src.infrastructure.repositories.client_api_key_repository import ClientApiKeyRepository
from src.usecases.client_api_key_usecase import ClientApiKeyUseCase


def make_api_key(**kwargs: object) -> ClientApiKey:
    defaults = {
        "id": "key_id",
        "client_id": uuid.uuid4(),
        "hashed_secret": "dummy_hashed_secret",
        "algorithm": ClientApiKeyAlgorithm.SHA256,
        "status": ClientApiKeyStatus.ACTIVE,
        "rotated_at": None,
        "expires_at": None,
    }
    defaults.update(kwargs)
    return ClientApiKey(**defaults)


class TestClientApiKeyUseCase:
    @pytest.fixture
    def mock_client_api_key_repository(self, mocker: MockFixture):
        return mocker.Mock(spec=ClientApiKeyRepository)

    @pytest.fixture
    def mock_client_api_key_service(self, mocker: MockFixture):
        return mocker.Mock(spec=ClientApiKeyService)

    @pytest.fixture
    def mock_connection(self, mocker: MockFixture):
        mock = mocker.Mock(spec=Connection)
        mock.transaction.return_value = mocker.MagicMock()
        return mock

    @pytest.fixture
    def usecase(
        self,
        mock_client_api_key_repository: ClientApiKeyRepository,
        mock_client_api_key_service: ClientApiKeyService,
        mock_connection: Connection,
    ):
        return ClientApiKeyUseCase(
            api_key_repository=mock_client_api_key_repository,
            api_key_service=mock_client_api_key_service,
            connection=mock_connection,
        )

    class TestRotate:
        def test_rotate_success(
            self,
            usecase: ClientApiKeyUseCase,
            mock_client_api_key_service: Mock,
            mock_client: Client,
        ):
            """APIキーのローテーションが成功し、新しいキーと有効期限が返されることを確認する"""
            # Arrange
            expected_plain_key = "plain api key"
            expected_expires_at = datetime(2024, 1, 10, 12, 0, tzinfo=ZoneInfo("Asia/Tokyo"))

            # Mock
            mock_client_api_key_service.issue.return_value.entity = make_api_key(
                id="new_key_id",
                expires_at=expected_expires_at,
            )
            mock_client_api_key_service.issue.return_value.plain_key = SecretStr(expected_plain_key)

            # Act
            result = usecase.rotate(mock_client, 7, make_api_key())

            # Assert
            assert result.key.get_secret_value() == expected_plain_key
            assert result.expires_at == expected_expires_at

        def test_error_with_already_rotated_key(
            self,
            usecase: ClientApiKeyUseCase,
            mock_client: Client,
        ):
            """rotated_atが設定されている場合、ClientErrorが発生することを確認する"""
            # Arrange
            already_rotated_key = make_api_key(
                id="already_rotated_key_id",
                status=ClientApiKeyStatus.DEPRECATED,
                rotated_at=datetime(2024, 1, 1, 12, 0, tzinfo=ZoneInfo("Asia/Tokyo")),
            )

            # Act
            with pytest.raises(ClientError, match=r"This API key has been rotated. Use the new key."):
                usecase.rotate(mock_client, None, already_rotated_key)
