import uuid
from unittest.mock import Mock

import pytest
from psycopg import Connection
from pytest_mock import MockFixture
from src.__types.errors import AlreadyInitializedError, ResourceNotFoundError
from src.__types.index import Distribution, Tenant
from src.infrastructure.credential_manager import CredentialManager
from src.infrastructure.repositories.tenant_repository import TenantRepository
from src.infrastructure.repositories.user_identity_repository import UserIdentityRepository
from src.usecases.management_console_usecase import ManagementConsoleUseCase


class TestManagementConsoleUseCase:
    @pytest.fixture
    def mock_connection(self, mocker: MockFixture) -> Mock:
        return mocker.Mock(spec=Connection)

    @pytest.fixture
    def mock_tenant_repository(self, mocker: MockFixture) -> Mock:
        return mocker.Mock(spec=TenantRepository)

    @pytest.fixture
    def mock_credential_manager(self, mocker: MockFixture) -> Mock:
        return mocker.Mock(spec=CredentialManager)

    @pytest.fixture
    def mock_user_identity_repository_factory(self, mocker: MockFixture) -> Mock:
        return mocker.Mock(return_value=mocker.Mock(spec=UserIdentityRepository))

    @pytest.fixture
    def usecase(
        self,
        mock_connection: Mock,
        mock_tenant_repository: Mock,
        mock_credential_manager: Mock,
        mock_user_identity_repository_factory: Mock,
    ) -> ManagementConsoleUseCase:
        return ManagementConsoleUseCase(
            connection=mock_connection,
            tenant_repository=mock_tenant_repository,
            credential_manager=mock_credential_manager,
            user_identity_repository_factory=mock_user_identity_repository_factory,
        )

    @pytest.fixture
    def uninitialized_tenant(self) -> Tenant:
        return Tenant(
            id=uuid.uuid4(),
            legal_name="テスト株式会社",
            name=None,
            m2m_application_client_id="m2m-client-id",
            user_application_client_id=None,
            whi_customer_id=["whi#1"],
            cydas_customer_id=None,
            allowed_m2m_metadata=[],
        )

    class TestCreate:
        def test_creates_usecase_with_tenant_repository(self, mocker: MockFixture) -> None:
            """createファクトリでTenantRepositoryが生成されることを確認する。"""
            # Arrange
            mock_connection = mocker.Mock(spec=Connection)
            mock_credential_manager = mocker.Mock(spec=CredentialManager)
            mock_factory = mocker.Mock()

            # Act
            actual = ManagementConsoleUseCase.create(
                connection=mock_connection,
                credential_manager=mock_credential_manager,
                user_identity_repository_factory=mock_factory,
            )

            # Assert
            assert isinstance(actual, ManagementConsoleUseCase)
            assert actual.credential_manager == mock_credential_manager
            assert actual.user_identity_repository_factory == mock_factory

    class TestInitialize:
        def test_resolves_tenant_by_whi_customer_id(
            self,
            usecase: ManagementConsoleUseCase,
            mock_tenant_repository: Mock,
            mock_user_identity_repository_factory: Mock,
            uninitialized_tenant: Tenant,
            mocker: MockFixture,
        ) -> None:
            """WHI取引先IDでテナントが特定され初期化が実行されることを確認する。"""
            # Arrange
            mock_tenant_repository.get_by_customer_id.return_value = uninitialized_tenant
            mock_service_init = mocker.patch(
                "src.usecases.management_console_usecase.ManagementConsoleService.initialize",
            )

            # Act
            usecase.initialize(
                whi_customer_ids=["whi#1"],
                cydas_customer_ids=[],
                tenant_name="テスト管理画面",
                user_emails=["admin@example.com"],
            )

            # Assert
            mock_tenant_repository.get_by_customer_id.assert_called_once_with(
                distribution=Distribution.WHI_SFDC,
                customer_id="whi#1",
            )
            mock_user_identity_repository_factory.assert_called_once_with(uninitialized_tenant)
            mock_service_init.assert_called_once()

        @pytest.mark.usefixtures("mock_user_identity_repository_factory")
        def test_resolves_tenant_by_cydas_customer_id(
            self,
            usecase: ManagementConsoleUseCase,
            mock_tenant_repository: Mock,
            uninitialized_tenant: Tenant,
            mocker: MockFixture,
        ) -> None:
            """CYDAS取引先IDでテナントが特定され初期化が実行されることを確認する。"""
            # Arrange
            mock_tenant_repository.get_by_customer_id.return_value = uninitialized_tenant
            mock_service_init = mocker.patch(
                "src.usecases.management_console_usecase.ManagementConsoleService.initialize",
            )

            # Act
            usecase.initialize(
                whi_customer_ids=[],
                cydas_customer_ids=["cydas#1"],
                tenant_name="テスト管理画面",
                user_emails=["admin@example.com"],
            )

            # Assert
            mock_tenant_repository.get_by_customer_id.assert_called_once_with(
                distribution=Distribution.CYDAS_SFDC,
                customer_id="cydas#1",
            )
            mock_service_init.assert_called_once()

        def test_raises_when_no_tenant_found(
            self,
            usecase: ManagementConsoleUseCase,
            mock_tenant_repository: Mock,
        ) -> None:
            """テナントが見つからない場合にResourceNotFoundErrorが発生することを確認する。"""
            # Arrange
            mock_tenant_repository.get_by_customer_id.side_effect = ResourceNotFoundError("not found")

            # Act & Assert
            with pytest.raises(ResourceNotFoundError):
                usecase.initialize(
                    whi_customer_ids=["whi#x"],
                    cydas_customer_ids=["cydas#x"],
                    tenant_name="テスト",
                    user_emails=["a@b.com"],
                )

        def test_raises_when_already_initialized_with_same_name(
            self,
            usecase: ManagementConsoleUseCase,
            mock_tenant_repository: Mock,
        ) -> None:
            """同名で初期化済みの場合にAlreadyInitializedErrorが発生することを確認する。"""
            # Arrange
            tenant = Tenant(
                id=uuid.uuid4(),
                legal_name="テスト株式会社",
                name="テスト管理画面",
                m2m_application_client_id="m2m-id",
                user_application_client_id="user-app-id",
                whi_customer_id=["whi#1"],
                cydas_customer_id=None,
                allowed_m2m_metadata=[],
            )
            mock_tenant_repository.get_by_customer_id.return_value = tenant

            # Act & Assert
            with pytest.raises(AlreadyInitializedError):
                usecase.initialize(
                    whi_customer_ids=["whi#1"],
                    cydas_customer_ids=[],
                    tenant_name="テスト管理画面",
                    user_emails=["a@b.com"],
                )

        def test_raises_when_already_initialized_with_different_name(
            self,
            usecase: ManagementConsoleUseCase,
            mock_tenant_repository: Mock,
        ) -> None:
            """異なる名前で初期化済みの場合にAlreadyInitializedErrorが発生することを確認する。"""
            # Arrange
            tenant = Tenant(
                id=uuid.uuid4(),
                legal_name="テスト株式会社",
                name="既存の名前",
                m2m_application_client_id="m2m-id",
                user_application_client_id="user-app-id",
                whi_customer_id=["whi#1"],
                cydas_customer_id=None,
                allowed_m2m_metadata=[],
            )
            mock_tenant_repository.get_by_customer_id.return_value = tenant

            # Act & Assert
            with pytest.raises(AlreadyInitializedError):
                usecase.initialize(
                    whi_customer_ids=["whi#1"],
                    cydas_customer_ids=[],
                    tenant_name="新しい名前",
                    user_emails=["a@b.com"],
                )
