"""Tests for AWS SES backend."""

import json
import types
from unittest.mock import patch

import pytest

from amsdal_mail.exceptions import ConfigurationError
from amsdal_mail.message import EmailMessage

_fake_boto3 = types.ModuleType('boto3')
_fake_boto3.client = None  # type: ignore[attr-defined]

_fake_aioboto3 = types.ModuleType('aioboto3')
_fake_aioboto3.Session = None  # type: ignore[attr-defined]


@pytest.fixture(autouse=True)
def _mock_boto3():
    """Ensure boto3/aioboto3 are importable so SESBackend.__init__ doesn't raise ImportError."""
    with patch.dict(
        'sys.modules',
        {'boto3': _fake_boto3, 'aioboto3': _fake_aioboto3},
    ):
        # Reload the module so it picks up the fake boto3
        import importlib

        import amsdal_mail.backends.ses as ses_mod

        importlib.reload(ses_mod)
        yield ses_mod
        # Reload again to restore original state
        importlib.reload(ses_mod)


def _get_backend_cls(ses_mod):
    return ses_mod.SESBackend


class TestSESBackend:
    """Tests for SESBackend."""

    def test_ses_backend_requires_credentials(self, _mock_boto3, monkeypatch):
        monkeypatch.delenv('AWS_ACCESS_KEY_ID', raising=False)
        monkeypatch.delenv('AWS_SECRET_ACCESS_KEY', raising=False)

        cls = _get_backend_cls(_mock_boto3)
        with pytest.raises(ConfigurationError, match='AWS credentials are required'):
            cls()

    def test_ses_backend_with_credentials(self, _mock_boto3, monkeypatch):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        backend = _get_backend_cls(_mock_boto3)()

        assert backend.aws_access_key_id == 'test-key-id'
        assert backend.aws_secret_access_key == 'test-secret-key'
        assert backend.region_name == 'us-east-1'

    def test_ses_backend_with_custom_region(self, _mock_boto3, monkeypatch):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        backend = _get_backend_cls(_mock_boto3)(region_name='eu-west-1')

        assert backend.region_name == 'eu-west-1'

    def test_ses_backend_with_configuration_set(self, _mock_boto3, monkeypatch):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        backend = _get_backend_cls(_mock_boto3)(configuration_set='my-config-set')

        assert backend.configuration_set == 'my-config-set'

    def test_ses_backend_send_messages_mock(self, _mock_boto3, monkeypatch, mocker):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        mock_client = mocker.MagicMock()
        mock_client.send_raw_email.return_value = {'MessageId': 'test-message-id'}

        mocker.patch.object(_mock_boto3, 'boto3')
        _mock_boto3.boto3.client.return_value = mock_client

        backend = _get_backend_cls(_mock_boto3)()

        message = EmailMessage(
            subject='Test',
            body='Test body',
            from_email='sender@example.com',
            to=['recipient@example.com'],
        )

        status = backend.send_messages([message])

        assert status.is_success
        assert len(status.recipients) == 1
        mock_client.send_raw_email.assert_called_once()

        call_args = mock_client.send_raw_email.call_args[1]
        assert call_args['Source'] == 'sender@example.com'
        assert call_args['Destinations'] == ['recipient@example.com']
        assert 'RawMessage' in call_args

    def test_ses_backend_send_multiple_messages(self, _mock_boto3, monkeypatch, mocker):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        mock_client = mocker.MagicMock()
        mock_client.send_raw_email.return_value = {'MessageId': 'test-message-id'}

        mocker.patch.object(_mock_boto3, 'boto3')
        _mock_boto3.boto3.client.return_value = mock_client

        backend = _get_backend_cls(_mock_boto3)()

        messages = [
            EmailMessage(
                subject=f'Test {i}',
                body=f'Body {i}',
                from_email='sender@example.com',
                to=['recipient@example.com'],
            )
            for i in range(3)
        ]

        status = backend.send_messages(messages)

        assert status.is_success
        assert len(status.recipients) == 1
        assert mock_client.send_raw_email.call_count == 3

    def test_ses_backend_with_configuration_set_in_call(self, _mock_boto3, monkeypatch, mocker):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        mock_client = mocker.MagicMock()
        mock_client.send_raw_email.return_value = {'MessageId': 'test-message-id'}

        mocker.patch.object(_mock_boto3, 'boto3')
        _mock_boto3.boto3.client.return_value = mock_client

        backend = _get_backend_cls(_mock_boto3)(configuration_set='my-config')

        message = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['recipient@example.com'],
        )

        backend.send_messages([message])

        call_args = mock_client.send_raw_email.call_args[1]
        assert call_args['ConfigurationSetName'] == 'my-config'

    def test_ses_backend_fail_silently(self, _mock_boto3, monkeypatch, mocker):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        mock_client = mocker.MagicMock()
        mock_client.send_raw_email.side_effect = Exception('API Error')

        mocker.patch.object(_mock_boto3, 'boto3')
        _mock_boto3.boto3.client.return_value = mock_client

        backend = _get_backend_cls(_mock_boto3)(fail_silently=True)

        message = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['recipient@example.com'],
        )

        status = backend.send_messages([message])
        assert len(status.recipients) == 1
        assert status.has_failures
        assert status.recipients['recipient@example.com'].status == 'failed'

    @pytest.mark.asyncio
    async def test_ses_backend_async_send(self, _mock_boto3, monkeypatch, mocker):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        mock_client = mocker.AsyncMock()
        mock_client.send_raw_email.return_value = {'MessageId': 'test-message-id'}

        mock_session = mocker.MagicMock()
        mock_session.client.return_value.__aenter__.return_value = mock_client
        mock_session.client.return_value.__aexit__.return_value = None

        mocker.patch.object(_mock_boto3, 'aioboto3')
        _mock_boto3.aioboto3.Session.return_value = mock_session

        backend = _get_backend_cls(_mock_boto3)()

        message = EmailMessage(
            subject='Test',
            body='Test body',
            from_email='sender@example.com',
            to=['recipient@example.com'],
        )

        status = await backend.asend_messages([message])

        assert status.is_success
        assert len(status.recipients) == 1
        mock_client.send_raw_email.assert_awaited_once()

    def test_ses_backend_in_registry(self):
        from amsdal_mail.backends import BACKENDS

        assert 'ses' in BACKENDS
        assert BACKENDS['ses'] == 'amsdal_mail.backends.ses.SESBackend'

    def test_ses_backend_via_get_connection(self, _mock_boto3, monkeypatch):
        from amsdal_mail import get_connection

        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')
        monkeypatch.setenv('AMSDAL_EMAIL_BACKEND', 'ses')

        backend = get_connection()

        assert type(backend).__name__ == 'SESBackend'
        assert backend.aws_access_key_id == 'test-key-id'  # type: ignore[attr-defined]

    def test_ses_backend_with_tags(self, _mock_boto3, monkeypatch, mocker):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        mock_client = mocker.MagicMock()
        mock_client.send_raw_email.return_value = {'MessageId': 'test-message-id'}

        mocker.patch.object(_mock_boto3, 'boto3')
        _mock_boto3.boto3.client.return_value = mock_client

        backend = _get_backend_cls(_mock_boto3)()

        message = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['recipient@example.com'],
            tags=['marketing', 'newsletter'],
        )

        backend.send_messages([message])

        call_args = mock_client.send_raw_email.call_args[1]
        assert 'Tags' in call_args
        assert len(call_args['Tags']) == 2
        assert {'Name': 'marketing', 'Value': 'true'} in call_args['Tags']
        assert {'Name': 'newsletter', 'Value': 'true'} in call_args['Tags']

    def test_ses_backend_with_metadata(self, _mock_boto3, monkeypatch, mocker):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        mock_client = mocker.MagicMock()
        mock_client.send_raw_email.return_value = {'MessageId': 'test-message-id'}

        mocker.patch.object(_mock_boto3, 'boto3')
        _mock_boto3.boto3.client.return_value = mock_client

        backend = _get_backend_cls(_mock_boto3)()

        message = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['recipient@example.com'],
            metadata={'user_id': '123', 'campaign': 'summer'},
        )

        backend.send_messages([message])

        call_args = mock_client.send_raw_email.call_args[1]
        assert 'Tags' in call_args
        assert len(call_args['Tags']) == 2
        assert {'Name': 'metadata:user_id', 'Value': '123'} in call_args['Tags']
        assert {'Name': 'metadata:campaign', 'Value': 'summer'} in call_args['Tags']

    def test_ses_backend_with_tags_and_metadata(self, _mock_boto3, monkeypatch, mocker):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        mock_client = mocker.MagicMock()
        mock_client.send_raw_email.return_value = {'MessageId': 'test-message-id'}

        mocker.patch.object(_mock_boto3, 'boto3')
        _mock_boto3.boto3.client.return_value = mock_client

        backend = _get_backend_cls(_mock_boto3)()

        message = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['recipient@example.com'],
            tags=['welcome'],
            metadata={'user_id': '999'},
        )

        backend.send_messages([message])

        call_args = mock_client.send_raw_email.call_args[1]
        assert 'Tags' in call_args
        assert len(call_args['Tags']) == 2
        assert {'Name': 'welcome', 'Value': 'true'} in call_args['Tags']
        assert {'Name': 'metadata:user_id', 'Value': '999'} in call_args['Tags']

    @pytest.mark.asyncio
    async def test_ses_backend_async_with_tags(self, _mock_boto3, monkeypatch, mocker):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        mock_client = mocker.AsyncMock()
        mock_client.send_raw_email.return_value = {'MessageId': 'test-message-id'}

        mock_session = mocker.MagicMock()
        mock_session.client.return_value.__aenter__.return_value = mock_client
        mock_session.client.return_value.__aexit__.return_value = None

        mocker.patch.object(_mock_boto3, 'aioboto3')
        _mock_boto3.aioboto3.Session.return_value = mock_session

        backend = _get_backend_cls(_mock_boto3)()

        message = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['recipient@example.com'],
            tags=['async-test'],
        )

        await backend.asend_messages([message])

        call_args = mock_client.send_raw_email.call_args[1]
        assert 'Tags' in call_args
        assert {'Name': 'async-test', 'Value': 'true'} in call_args['Tags']

    def test_ses_backend_with_template(self, _mock_boto3, monkeypatch, mocker):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        mock_client = mocker.MagicMock()
        mock_client.send_templated_email.return_value = {'MessageId': 'test-message-id'}

        mocker.patch.object(_mock_boto3, 'boto3')
        _mock_boto3.boto3.client.return_value = mock_client

        backend = _get_backend_cls(_mock_boto3)()

        message = EmailMessage(
            subject='Welcome!',
            from_email='sender@example.com',
            to=['user@example.com'],
            template_id='welcome-template',
            merge_global_data={'company': 'Acme Inc'},
        )

        status = backend.send_messages([message])

        assert status.is_success
        assert len(status.recipients) == 1
        mock_client.send_templated_email.assert_called_once()

        call_args = mock_client.send_templated_email.call_args[1]
        assert call_args['Source'] == 'sender@example.com'
        assert call_args['Template'] == 'welcome-template'
        assert 'company' in call_args['TemplateData']

    def test_ses_backend_template_with_merge_data(self, _mock_boto3, monkeypatch, mocker):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        mock_client = mocker.MagicMock()
        mock_client.send_templated_email.return_value = {'MessageId': 'test-message-id'}

        mocker.patch.object(_mock_boto3, 'boto3')
        _mock_boto3.boto3.client.return_value = mock_client

        backend = _get_backend_cls(_mock_boto3)()

        message = EmailMessage(
            subject='Order Confirmation',
            from_email='orders@example.com',
            to=['customer@example.com'],
            template_id='order-template',
            merge_data={
                'customer@example.com': {'name': 'John', 'order_id': '12345'},
            },
            merge_global_data={'company': 'Acme Inc'},
        )

        backend.send_messages([message])

        call_args = mock_client.send_templated_email.call_args[1]
        template_data = json.loads(call_args['TemplateData'])
        assert template_data['name'] == 'John'
        assert template_data['order_id'] == '12345'
        assert template_data['company'] == 'Acme Inc'

    def test_ses_backend_template_with_tags(self, _mock_boto3, monkeypatch, mocker):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        mock_client = mocker.MagicMock()
        mock_client.send_templated_email.return_value = {'MessageId': 'test-message-id'}

        mocker.patch.object(_mock_boto3, 'boto3')
        _mock_boto3.boto3.client.return_value = mock_client

        backend = _get_backend_cls(_mock_boto3)()

        message = EmailMessage(
            subject='Newsletter',
            from_email='news@example.com',
            to=['subscriber@example.com'],
            template_id='newsletter-template',
            tags=['newsletter', 'marketing'],
        )

        backend.send_messages([message])

        call_args = mock_client.send_templated_email.call_args[1]
        assert 'Tags' in call_args
        assert {'Name': 'newsletter', 'Value': 'true'} in call_args['Tags']
        assert {'Name': 'marketing', 'Value': 'true'} in call_args['Tags']

    @pytest.mark.asyncio
    async def test_ses_backend_async_with_template(self, _mock_boto3, monkeypatch, mocker):
        monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'test-key-id')
        monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'test-secret-key')

        mock_client = mocker.AsyncMock()
        mock_client.send_templated_email.return_value = {'MessageId': 'test-message-id'}

        mock_session = mocker.MagicMock()
        mock_session.client.return_value.__aenter__.return_value = mock_client
        mock_session.client.return_value.__aexit__.return_value = None

        mocker.patch.object(_mock_boto3, 'aioboto3')
        _mock_boto3.aioboto3.Session.return_value = mock_session

        backend = _get_backend_cls(_mock_boto3)()

        message = EmailMessage(
            subject='Welcome',
            from_email='sender@example.com',
            to=['user@example.com'],
            template_id='welcome-template',
            merge_global_data={'company': 'Test Corp'},
        )

        status = await backend.asend_messages([message])

        assert status.is_success
        assert len(status.recipients) == 1
        mock_client.send_templated_email.assert_awaited_once()

        call_args = mock_client.send_templated_email.call_args[1]
        assert call_args['Template'] == 'welcome-template'
