from unittest.mock import Mock

import pytest
import src.management_pre_token_generation_trigger as trigger
from psycopg import Connection
from pytest_mock import MockFixture
from src.__types.index import Tenant


class TestManagementPreTokenGenerationTrigger:
    @pytest.fixture
    def connection(self, mocker: MockFixture):
        return mocker.Mock(spec=Connection)

    @pytest.fixture
    def mock_connect(self, mocker: MockFixture, connection: Connection) -> Mock:
        mock_connect = mocker.patch("src.management_pre_token_generation_trigger.psycopg.connect")
        mock_connect.return_value.__enter__.return_value = connection
        return mock_connect

    def test_handler_success(
        self,
        mocker: MockFixture,
        mock_tenant: Tenant,
        mock_connect: Mock,
    ):
        """正常系: tenant_id を追加し client_tenant の検証も通ることを確認する"""
        # Arrange
        event = {
            "callerContext": {"clientId": "app-client-id"},
            "request": {
                "clientMetadata": {"callerService": "ctm", "tenantName": "test_tenant2", "extra_key": "extra_value"},
            },
        }

        # Mock
        mock_tenant_repository = mocker.Mock()
        mock_tenant_repository.get_by_m2m_application_client_id.return_value = mock_tenant
        mocker.patch(
            "src.management_pre_token_generation_trigger.TenantRepository",
            return_value=mock_tenant_repository,
        )

        # Act
        result = trigger.handler(event, mocker.Mock())

        # Assert
        mock_connect.assert_called_once()
        assert result["response"]["claimsAndScopeOverrideDetails"]["accessTokenGeneration"]["claimsToAddOrOverride"][
            "tenant_id"
        ] == str(mock_tenant.id)

    def test_raises_when_missing_client_id(self, mocker: MockFixture, mock_connect: Mock):
        """ClientId がない場合に KeyError が発生することを確認する"""
        # Arrange
        event = {
            "callerContext": {},
            "request": {"clientMetadata": {"tenantName": "dummy_client_tenant"}},
        }

        # Act & Assert
        with pytest.raises(KeyError, match=r"application_client_id is required in the event request."):
            trigger.handler(event, mocker.Mock())
        mock_connect.assert_called_once()

    def test_raises_when_metadata_verification_failed(
        self,
        mocker: MockFixture,
        mock_tenant: Tenant,
        mock_connect: Mock,
    ):
        """メタデータの検証に失敗した場合に例外が発生することを確認する"""
        # Arrange
        event = {
            "callerContext": {"clientId": "app-client-id"},
            "request": {"clientMetadata": {"callerService": "ctm", "tenantName": "wrong_tenant"}},
        }

        # Mock
        mock_tenant_repository = mocker.Mock()
        mock_tenant_repository.get_by_m2m_application_client_id.return_value = mock_tenant
        mocker.patch(
            "src.management_pre_token_generation_trigger.TenantRepository",
            return_value=mock_tenant_repository,
        )

        # Act & Assert
        with pytest.raises(ValueError, match=r"Invalid client metadata provided."):
            trigger.handler(event, mocker.Mock())
        mock_connect.assert_called_once()

    def test_raises_when_missing_caller_service(
        self,
        mocker: MockFixture,
        mock_tenant: Tenant,
        mock_connect: Mock,
    ):
        """CallerService がない場合にメタデータ検証で ValueError が発生することを確認する"""
        # Arrange
        event = {
            "callerContext": {"clientId": "app-client-id"},
            "request": {"clientMetadata": {"tenantName": "test_tenant2"}},
        }

        # Mock
        mock_tenant_repository = mocker.Mock()
        mock_tenant_repository.get_by_m2m_application_client_id.return_value = mock_tenant
        mocker.patch(
            "src.management_pre_token_generation_trigger.TenantRepository",
            return_value=mock_tenant_repository,
        )

        # Act & Assert
        with pytest.raises(ValueError, match=r"Invalid client metadata provided."):
            trigger.handler(event, mocker.Mock())
        mock_connect.assert_called_once()

    def test_raises_when_missing_metadata(
        self,
        mocker: MockFixture,
        mock_tenant: Tenant,
        mock_connect: Mock,
    ):
        """Metadata がない場合にエラーログを出力し ValueError が発生することを確認する"""
        # Arrange
        event = {
            "callerContext": {"clientId": "app-client-id"},
        }

        # Mock
        mock_tenant_repository = mocker.Mock()
        mock_tenant_repository.get_by_m2m_application_client_id.return_value = mock_tenant
        mocker.patch(
            "src.management_pre_token_generation_trigger.TenantRepository",
            return_value=mock_tenant_repository,
        )

        error_mock = mocker.patch("src.management_pre_token_generation_trigger.logger.error")

        # Act & Assert
        with pytest.raises(ValueError, match=r"Client metadata is required."):
            trigger.handler(event, mocker.Mock())

        error_mock.assert_called_once_with(
            "client_metadata_required",
            tenant_legal_name=mock_tenant.legal_name,
        )
        mock_connect.assert_called_once()
