import uuid
from unittest.mock import Mock

import pytest
from pytest_mock import MockFixture
from src.__types.errors import ResourceNotFoundError
from src.__types.index import Client, Distribution, PendingClientTenant, Tenant
from src.domain.services.client_tenant_service import ClientTenantService
from src.infrastructure.repositories.client_tenant_repository import ClientTenantRepository
from src.infrastructure.repositories.pending_client_tenant_repository import PendingClientTenantRepository
from src.infrastructure.repositories.tenant_repository import TenantRepository


class TestClientTenantService:
    @pytest.fixture
    def mock_client_tenant_repository(self, mocker: MockFixture) -> Mock:
        return mocker.Mock(spec=ClientTenantRepository)

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

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

    @pytest.fixture
    def service(
        self,
        mock_client_tenant_repository: Mock,
        mock_pending_client_tenant_repository: Mock,
        mock_tenant_repository: Mock,
    ) -> ClientTenantService:
        return ClientTenantService(
            client_tenant_repository=mock_client_tenant_repository,
            pending_client_tenant_repository=mock_pending_client_tenant_repository,
            tenant_repository=mock_tenant_repository,
        )

    class TestAdd:
        def test_tenant_exists(
            self,
            service: ClientTenantService,
            mock_client_tenant_repository: Mock,
            mock_pending_client_tenant_repository: Mock,
            mock_tenant_repository: Mock,
            mock_client: Client,
            mock_tenant: Tenant,
        ) -> None:
            """Tenantと紐付け可能な場合にClientTenantの作成ができることを確認する。"""
            # Arrange
            mock_tenant_repository.get_by_customer_id.return_value = mock_tenant

            # Act
            service.add(
                client=mock_client,
                name="dummy_tenant_name",
                distribution=Distribution.WHI_SFDC,
                customer_id="dummy_customer_id",
                base_url="https://dummy.com",
            )

            # Assert
            mock_tenant_repository.get_by_customer_id.assert_called_once_with(
                distribution=Distribution.WHI_SFDC,
                customer_id="dummy_customer_id",
            )
            mock_client_tenant_repository.add.assert_called_once_with(
                tenant_id=mock_tenant.id,
                client_id=mock_client.id,
                name="dummy_tenant_name",
                distribution=Distribution.WHI_SFDC,
                customer_id="dummy_customer_id",
                base_url="https://dummy.com",
            )
            mock_pending_client_tenant_repository.add.assert_not_called()

        def test_tenant_not_found(
            self,
            service: ClientTenantService,
            mock_client_tenant_repository: Mock,
            mock_pending_client_tenant_repository: Mock,
            mock_tenant_repository: Mock,
            mock_client: Client,
        ) -> None:
            """Tenantと紐付け不可能なPendingClientTenantの作成ができることを確認する。"""
            # Arrange
            mock_tenant_repository.get_by_customer_id.side_effect = ResourceNotFoundError("not found")

            # Act
            service.add(
                client=mock_client,
                name="dummy_tenant_name",
                distribution=Distribution.CYDAS_SFDC,
                customer_id="missing_customer_id",
                base_url=None,
            )

            # Assert
            mock_tenant_repository.get_by_customer_id.assert_called_once_with(
                distribution=Distribution.CYDAS_SFDC,
                customer_id="missing_customer_id",
            )
            mock_pending_client_tenant_repository.add.assert_called_once_with(
                client_id=mock_client.id,
                name="dummy_tenant_name",
                distribution=Distribution.CYDAS_SFDC,
                customer_id="missing_customer_id",
                base_url=None,
            )
            mock_client_tenant_repository.add.assert_not_called()

    class TestTransfer:
        def test_pending_client_tenant(
            self,
            service: ClientTenantService,
            mock_client_tenant_repository: Mock,
            mock_pending_client_tenant_repository: Mock,
        ) -> None:
            """PendingClientTenantをClientTenantに移行できることを確認する。"""
            # Arrange
            tenant_id = uuid.UUID("00000001-0000-0000-0000-000000000000")
            pending_client_tenants = [
                PendingClientTenant(
                    client_id=uuid.UUID("00000001-0000-0000-0000-000000000000"),
                    name="dummy_client_tenant_1",
                    distribution=Distribution.WHI_SFDC,
                    customer_id="whi#dummy",
                    base_url=None,
                ),
                PendingClientTenant(
                    client_id=uuid.UUID("00000002-0000-0000-0000-000000000000"),
                    name="dummy_client_tenant_2",
                    distribution=Distribution.WHI_SFDC,
                    customer_id="whi#dummy",
                    base_url="https://dummy.example.com",
                ),
            ]
            mock_pending_client_tenant_repository.find_by_customer_id.return_value = pending_client_tenants

            # Act
            service.transfer(
                tenant_id=tenant_id,
                distribution=Distribution.WHI_SFDC,
                customer_id="whi#dummy",
            )

            # Assert
            mock_pending_client_tenant_repository.find_by_customer_id.assert_called_once_with(
                distribution=Distribution.WHI_SFDC,
                customer_id="whi#dummy",
            )
            assert mock_client_tenant_repository.add.call_count == 2
            mock_client_tenant_repository.add.assert_any_call(
                tenant_id=tenant_id,
                client_id=uuid.UUID("00000001-0000-0000-0000-000000000000"),
                name="dummy_client_tenant_1",
                distribution=Distribution.WHI_SFDC,
                customer_id="whi#dummy",
                base_url=None,
            )
            mock_client_tenant_repository.add.assert_any_call(
                tenant_id=tenant_id,
                client_id=uuid.UUID("00000002-0000-0000-0000-000000000000"),
                name="dummy_client_tenant_2",
                distribution=Distribution.WHI_SFDC,
                customer_id="whi#dummy",
                base_url="https://dummy.example.com",
            )
            assert mock_pending_client_tenant_repository.delete.call_count == 2
            mock_pending_client_tenant_repository.delete.assert_any_call(
                client_id=uuid.UUID("00000001-0000-0000-0000-000000000000"),
                name="dummy_client_tenant_1",
            )
            mock_pending_client_tenant_repository.delete.assert_any_call(
                client_id=uuid.UUID("00000002-0000-0000-0000-000000000000"),
                name="dummy_client_tenant_2",
            )

        def test_no_pending_client_tenants(
            self,
            service: ClientTenantService,
            mock_client_tenant_repository: Mock,
            mock_pending_client_tenant_repository: Mock,
        ) -> None:
            """PendingClientTenantが存在しない場合に何も起こらないことを確認する。"""
            # Arrange
            mock_pending_client_tenant_repository.find_by_customer_id.return_value = []

            # Act
            service.transfer(
                tenant_id=uuid.UUID("00000001-0000-0000-0000-000000000000"),
                distribution=Distribution.CYDAS_SFDC,
                customer_id="cydas#missing",
            )

            # Assert
            mock_client_tenant_repository.add.assert_not_called()
            mock_pending_client_tenant_repository.delete.assert_not_called()
