import uuid
from unittest.mock import Mock

import pytest
from pytest_mock import MockFixture
from src.__types.errors import IdentityProviderError, ResourceDuplicatedError
from src.__types.index import User, UserWriteAttribute
from src.domain.services.user_service import UserService
from src.infrastructure.repositories.user_identity_repository import UserIdentityRepository
from src.infrastructure.repositories.user_repository import UserRepository


class TestUserService:
    @pytest.fixture
    def mock_user_repository(self, mocker: MockFixture) -> Mock:
        return mocker.Mock(spec=UserRepository)

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

    @pytest.fixture
    def service(
        self,
        mock_user_repository: Mock,
        mock_user_identity_repository: Mock,
    ) -> UserService:
        return UserService(
            user_repository=mock_user_repository,
            user_identity_repository=mock_user_identity_repository,
        )

    class TestBulkAdd:
        def test_creates_users_in_db_then_userpool(
            self,
            service: UserService,
            mock_user_repository: Mock,
            mock_user_identity_repository: Mock,
        ) -> None:
            """DB作成後にUserPool作成が順序通り実行されることを確認する。"""
            # Arrange
            users = [
                UserWriteAttribute(display_id="user1", display_name="User 1", email="user1@example.com"),
                UserWriteAttribute(display_id="user2", display_name="User 2", email="user2@example.com"),
            ]
            created_users = [
                User(id=uuid.uuid4(), display_id="user1", display_name="User 1", email="user1@example.com"),
                User(id=uuid.uuid4(), display_id="user2", display_name="User 2", email="user2@example.com"),
            ]
            mock_user_repository.bulk_create.return_value = created_users

            # Act
            service.bulk_add(users=users)

            # Assert
            mock_user_repository.bulk_create.assert_called_once_with(users=users)
            assert mock_user_identity_repository.create_user.call_count == 2
            mock_user_identity_repository.create_user.assert_any_call(user=created_users[0])
            mock_user_identity_repository.create_user.assert_any_call(user=created_users[1])

        def test_rollback_on_identity_provider_error(
            self,
            service: UserService,
            mock_user_repository: Mock,
            mock_user_identity_repository: Mock,
        ) -> None:
            """UserPool作成失敗時にDB・UserPoolのユーザーが削除されることを確認する。"""
            # Arrange
            user1 = User(id=uuid.uuid4(), display_id="user1", display_name="User 1", email="u1@example.com")
            user2 = User(id=uuid.uuid4(), display_id="user2", display_name="User 2", email="u2@example.com")
            mock_user_repository.bulk_create.return_value = [user1, user2]
            mock_user_identity_repository.create_user.side_effect = [None, IdentityProviderError("fail")]

            # Act & Assert
            with pytest.raises(IdentityProviderError):
                service.bulk_add(users=[])

            mock_user_repository.bulk_delete.assert_called_once_with(user_ids=[user1.id, user2.id])
            mock_user_identity_repository.delete_user.assert_called_once_with(user_id=user1.id)

        def test_rollback_on_resource_duplicated_error(
            self,
            service: UserService,
            mock_user_repository: Mock,
            mock_user_identity_repository: Mock,
        ) -> None:
            """ResourceDuplicatedError発生時にもロールバックが実行されることを確認する。"""
            # Arrange
            user1 = User(id=uuid.uuid4(), display_id="user1", display_name="User 1", email="u1@example.com")
            mock_user_repository.bulk_create.return_value = [user1]
            mock_user_identity_repository.create_user.side_effect = ResourceDuplicatedError("dup")

            # Act & Assert
            with pytest.raises(ResourceDuplicatedError):
                service.bulk_add(users=[])

            mock_user_repository.bulk_delete.assert_called_once_with(user_ids=[user1.id])
            mock_user_identity_repository.delete_user.assert_not_called()

        def test_cleanup_failure_does_not_suppress_original_error(
            self,
            service: UserService,
            mock_user_repository: Mock,
            mock_user_identity_repository: Mock,
        ) -> None:
            """クリーンアップ中の例外が元の例外を隠さないことを確認する。"""
            # Arrange
            user1 = User(id=uuid.uuid4(), display_id="user1", display_name="User 1", email="u1@example.com")
            user2 = User(id=uuid.uuid4(), display_id="user2", display_name="User 2", email="u2@example.com")
            mock_user_repository.bulk_create.return_value = [user1, user2]
            mock_user_identity_repository.create_user.side_effect = [None, IdentityProviderError("fail")]
            mock_user_repository.bulk_delete.side_effect = Exception("db cleanup fail")
            mock_user_identity_repository.delete_user.side_effect = Exception("userpool cleanup fail")

            # Act & Assert
            with pytest.raises(IdentityProviderError):
                service.bulk_add(users=[])
