from unittest.mock import Mock

import pytest
import src.management_user_custom_message_trigger as trigger
from psycopg import Connection
from pytest_mock import MockFixture
from src.__types.index import Tenant, User


class TestManagementUserCustomMessageTrigger:
    @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_user_custom_message_trigger.psycopg.connect")
        mock_connect.return_value.__enter__.return_value = connection
        return mock_connect

    @pytest.fixture
    def mock_settings(self, mocker: MockFixture) -> Mock:
        mock_settings = mocker.patch("src.management_user_custom_message_trigger.settings")
        mock_settings.postgres_url = "postgresql://test:test@localhost/test"
        mock_settings.management_console_domain = "console.example.com"
        return mock_settings

    def _build_event(
        self,
        trigger_source: str,
        tenant_id: str,
        user_id: str = "00000000-0000-0000-0000-000000000002",
    ) -> dict:
        return {
            "triggerSource": trigger_source,
            "request": {
                "userAttributes": {
                    "custom:tenant_id": tenant_id,
                    "custom:user_id": user_id,
                },
                "codeParameter": "{####}",
            },
        }

    @pytest.mark.usefixtures("mock_settings")
    def test_admin_create_user_success(
        self,
        mocker: MockFixture,
        mock_tenant: Tenant,
        mock_user: User,
        mock_connect: Mock,
    ):
        """正常系: AdminCreateUser で招待メールが生成されることを確認する"""
        # Arrange
        event = self._build_event("CustomMessage_AdminCreateUser", str(mock_tenant.id), str(mock_user.id))

        mock_tenant_repo = mocker.Mock()
        mock_tenant_repo.get.return_value = mock_tenant
        mocker.patch(
            "src.management_user_custom_message_trigger.TenantRepository",
            return_value=mock_tenant_repo,
        )

        mock_user_repo = mocker.Mock()
        mock_user_repo.get.return_value = mock_user
        mocker.patch(
            "src.management_user_custom_message_trigger.UserRepository",
            return_value=mock_user_repo,
        )

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

        # Assert
        mock_connect.assert_called_once()
        mock_user_repo.get.assert_called_once_with(user_id=mock_user.id)
        assert "emailSubject" in result["response"]
        assert mock_user.display_id in result["response"]["emailMessage"]
        assert mock_tenant.name in result["response"]["emailMessage"]

    @pytest.mark.usefixtures("mock_settings")
    def test_forgot_password_success(
        self,
        mocker: MockFixture,
        mock_tenant: Tenant,
        mock_user: User,
        mock_connect: Mock,
    ):
        """正常系: ForgotPassword でリセットメールが生成されることを確認する"""
        # Arrange
        event = self._build_event("CustomMessage_ForgotPassword", str(mock_tenant.id), str(mock_user.id))

        mock_tenant_repo = mocker.Mock()
        mock_tenant_repo.get.return_value = mock_tenant
        mocker.patch(
            "src.management_user_custom_message_trigger.TenantRepository",
            return_value=mock_tenant_repo,
        )

        mock_user_repo = mocker.Mock()
        mock_user_repo.get.return_value = mock_user
        mocker.patch(
            "src.management_user_custom_message_trigger.UserRepository",
            return_value=mock_user_repo,
        )

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

        # Assert
        mock_connect.assert_called_once()
        assert "reset-password" in result["response"]["emailMessage"]
        assert mock_tenant.name in result["response"]["emailMessage"]

    @pytest.mark.usefixtures("mock_settings")
    def test_unsupported_trigger_source_returns_event_unchanged(
        self,
        mocker: MockFixture,
        mock_connect: Mock,
    ):
        """対応外のtriggerSourceの場合、eventがそのまま返されることを確認する"""
        # Arrange
        event = {
            "triggerSource": "CustomMessage_SignUp",
            "request": {"userAttributes": {}},
        }

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

        # Assert
        assert result is event
        mock_connect.assert_not_called()

    @pytest.mark.usefixtures("mock_connect", "mock_settings")
    def test_raises_on_invalid_custom_attributes(
        self,
        mocker: MockFixture,
    ):
        """Custom attributesが不正な場合に例外が発生することを確認する"""
        # Arrange
        event = {
            "triggerSource": "CustomMessage_AdminCreateUser",
            "request": {"userAttributes": {}},
        }

        # Act & Assert
        with pytest.raises((KeyError, ValueError)):
            trigger.handler(event, mocker.Mock())

    @pytest.mark.usefixtures("mock_connect", "mock_settings")
    def test_raises_when_tenant_name_is_empty(
        self,
        mocker: MockFixture,
        mock_tenant: Tenant,
        mock_user: User,
    ):
        """tenant.nameが空の場合にValueErrorが発生することを確認する"""
        # Arrange
        mock_tenant_without_name = Tenant(
            id=mock_tenant.id,
            legal_name=mock_tenant.legal_name,
            name="",
            m2m_application_client_id=mock_tenant.m2m_application_client_id,
            whi_customer_id=None,
            cydas_customer_id=None,
            allowed_m2m_metadata=[],
        )
        event = self._build_event("CustomMessage_ForgotPassword", str(mock_tenant.id), str(mock_user.id))

        mock_tenant_repo = mocker.Mock()
        mock_tenant_repo.get.return_value = mock_tenant_without_name
        mocker.patch(
            "src.management_user_custom_message_trigger.TenantRepository",
            return_value=mock_tenant_repo,
        )

        mock_user_repo = mocker.Mock()
        mock_user_repo.get.return_value = mock_user
        mocker.patch(
            "src.management_user_custom_message_trigger.UserRepository",
            return_value=mock_user_repo,
        )

        # Act & Assert
        with pytest.raises(ValueError, match="Missing tenant name or management console domain"):
            trigger.handler(event, mocker.Mock())

    @pytest.mark.usefixtures("mock_connect")
    def test_raises_when_management_console_domain_is_empty(
        self,
        mocker: MockFixture,
        mock_tenant: Tenant,
        mock_user: User,
    ):
        """management_console_domainが空の場合にValueErrorが発生することを確認する"""
        # Arrange
        mock_settings = mocker.patch("src.management_user_custom_message_trigger.settings")
        mock_settings.postgres_url = "postgresql://test:test@localhost/test"
        mock_settings.management_console_domain = ""

        event = self._build_event("CustomMessage_ForgotPassword", str(mock_tenant.id), str(mock_user.id))

        mock_tenant_repo = mocker.Mock()
        mock_tenant_repo.get.return_value = mock_tenant
        mocker.patch(
            "src.management_user_custom_message_trigger.TenantRepository",
            return_value=mock_tenant_repo,
        )

        mock_user_repo = mocker.Mock()
        mock_user_repo.get.return_value = mock_user
        mocker.patch(
            "src.management_user_custom_message_trigger.UserRepository",
            return_value=mock_user_repo,
        )

        # Act & Assert
        with pytest.raises(ValueError, match="Missing tenant name or management console domain"):
            trigger.handler(event, mocker.Mock())
