import uuid
from unittest.mock import Mock

import pytest
from pytest_mock import MockFixture
from src.__types.index import Tenant, UserWriteAttribute
from src.domain.services.management_console_service import ManagementConsoleService
from src.domain.services.user_service import UserService
from src.infrastructure.credential_manager import CredentialManager
from src.infrastructure.repositories.tenant_repository import TenantRepository


class TestManagementConsoleService:
    @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_service(self, mocker: MockFixture) -> Mock:
        return mocker.Mock(spec=UserService)

    @pytest.fixture
    def service(
        self,
        mock_tenant_repository: Mock,
        mock_credential_manager: Mock,
        mock_user_service: Mock,
    ) -> ManagementConsoleService:
        return ManagementConsoleService(
            tenant_repository=mock_tenant_repository,
            credential_manager=mock_credential_manager,
            user_service=mock_user_service,
        )

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

    class TestInitialize:
        def test_initializes_management_console(
            self,
            service: ManagementConsoleService,
            mock_credential_manager: Mock,
            mock_tenant_repository: Mock,
            mock_user_service: Mock,
            tenant: Tenant,
        ) -> None:
            """認証クライアント発行→DB登録→ユーザー一括追加の順で実行されることを確認する。"""
            # Arrange
            mock_credential_manager.issue_user_login.return_value = "user-app-client-id"
            user_emails = ["admin@example.com", "user@example.com"]

            # Act
            service.initialize(tenant=tenant, tenant_name="テスト管理画面", user_emails=user_emails)

            # Assert
            mock_credential_manager.issue_user_login.assert_called_once_with(tenant.id)
            mock_tenant_repository.update_name_and_user_application_client_id.assert_called_once_with(
                tenant_id=tenant.id,
                name="テスト管理画面",
                user_application_client_id="user-app-client-id",
            )
            expected_users = [
                UserWriteAttribute(display_id="admin", display_name="admin", email="admin@example.com"),
                UserWriteAttribute(display_id="user", display_name="user", email="user@example.com"),
            ]
            mock_user_service.bulk_add.assert_called_once_with(users=expected_users)

        def test_cleans_up_credential_on_db_failure(
            self,
            service: ManagementConsoleService,
            mock_credential_manager: Mock,
            mock_tenant_repository: Mock,
            tenant: Tenant,
        ) -> None:
            """DB登録失敗時に発行済みクライアントが削除されることを確認する。"""
            # Arrange
            mock_credential_manager.issue_user_login.return_value = "user-app-client-id"
            mock_tenant_repository.update_name_and_user_application_client_id.side_effect = Exception("DB error")

            # Act & Assert
            with pytest.raises(Exception, match="DB error"):
                service.initialize(tenant=tenant, tenant_name="テスト", user_emails=["a@b.com"])

            mock_credential_manager.delete.assert_called_once_with("user-app-client-id")

        def test_cleans_up_tenant_and_credential_on_bulk_add_failure(
            self,
            service: ManagementConsoleService,
            mock_credential_manager: Mock,
            mock_tenant_repository: Mock,
            mock_user_service: Mock,
            tenant: Tenant,
        ) -> None:
            """ユーザー一括追加失敗時にテナントロールバックとクライアント削除が行われることを確認する。"""
            # Arrange
            mock_credential_manager.issue_user_login.return_value = "user-app-client-id"
            mock_user_service.bulk_add.side_effect = Exception("bulk_add error")

            # Act & Assert
            with pytest.raises(Exception, match="bulk_add error"):
                service.initialize(tenant=tenant, tenant_name="テスト", user_emails=["a@b.com"])

            mock_tenant_repository.update_name_and_user_application_client_id.assert_any_call(
                tenant_id=tenant.id,
                name=None,
                user_application_client_id=None,
            )
            mock_credential_manager.delete.assert_called_once_with("user-app-client-id")

        def test_credential_cleanup_failure_logged_and_suppressed(
            self,
            service: ManagementConsoleService,
            mock_credential_manager: Mock,
            mock_tenant_repository: Mock,
            tenant: Tenant,
            mocker: MockFixture,
        ) -> None:
            """DB登録失敗後のクリーンアップ失敗時にログ出力され、元の例外が伝播することを確認する。"""
            # Arrange
            mock_credential_manager.issue_user_login.return_value = "user-app-client-id"
            mock_tenant_repository.update_name_and_user_application_client_id.side_effect = Exception("DB error")
            mock_credential_manager.delete.side_effect = Exception("cleanup fail")
            mock_logger = mocker.patch("src.domain.services.management_console_service.logger")

            # Act & Assert
            with pytest.raises(Exception, match="DB error"):
                service.initialize(tenant=tenant, tenant_name="テスト", user_emails=["a@b.com"])

            mock_logger.exception.assert_called_once()

        def test_tenant_cleanup_failure_logged_and_suppressed(
            self,
            service: ManagementConsoleService,
            mock_credential_manager: Mock,
            mock_tenant_repository: Mock,
            mock_user_service: Mock,
            tenant: Tenant,
            mocker: MockFixture,
        ) -> None:
            """ユーザー追加失敗後のテナントロールバック失敗時にログ出力され、元の例外が伝播することを確認する。"""
            # Arrange
            mock_credential_manager.issue_user_login.return_value = "user-app-client-id"
            mock_user_service.bulk_add.side_effect = Exception("bulk_add error")
            # 1回目の呼び出し(initialize内)は成功、2回目(cleanup)で失敗
            mock_tenant_repository.update_name_and_user_application_client_id.side_effect = [
                None,
                Exception("rollback fail"),
            ]
            mock_logger = mocker.patch("src.domain.services.management_console_service.logger")

            # Act & Assert
            with pytest.raises(Exception, match="bulk_add error"):
                service.initialize(tenant=tenant, tenant_name="テスト", user_emails=["a@b.com"])

            mock_logger.exception.assert_called()
            mock_credential_manager.delete.assert_called_once_with("user-app-client-id")
