"""Tests for amsdal_mail message models."""

from email.message import Message
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

import pytest
from pydantic import ValidationError

from amsdal_mail.message import Attachment
from amsdal_mail.message import EmailMessage


class TestAttachment:
    """Tests for Attachment model."""

    def test_attachment_creation(self):
        """Test creating a basic attachment."""
        attachment = Attachment(
            filename='test.pdf',
            content=b'PDF content',
            mimetype='application/pdf',
        )

        assert attachment.filename == 'test.pdf'
        assert attachment.content == b'PDF content'
        assert attachment.mimetype == 'application/pdf'

    def test_attachment_with_content_id(self):
        """Test creating an inline attachment with content_id."""
        attachment = Attachment(
            filename='logo.png',
            content=b'PNG content',
            mimetype='image/png',
            content_id='logo',
        )

        assert attachment.filename == 'logo.png'
        assert attachment.content_id == 'logo'

    def test_attachment_content_id_default_none(self):
        """Test that content_id defaults to None."""
        attachment = Attachment(
            filename='test.pdf',
            content=b'PDF content',
            mimetype='application/pdf',
        )

        assert attachment.content_id is None

    def test_attachment_requires_all_fields(self):
        """Test that all fields are required."""
        with pytest.raises(ValidationError):
            Attachment(filename='test.pdf')  # type: ignore[call-arg]

        with pytest.raises(ValidationError):
            Attachment(content=b'test')  # type: ignore[call-arg]

        with pytest.raises(ValidationError):
            Attachment(mimetype='text/plain')  # type: ignore[call-arg]


class TestEmailMessage:
    """Tests for EmailMessage model."""

    def test_simple_email_creation(self):
        """Test creating a simple email message."""
        msg = EmailMessage(
            subject='Test Subject',
            body='Test body',
            from_email='sender@example.com',
            to=['recipient@example.com'],
        )

        assert msg.subject == 'Test Subject'
        assert msg.body == 'Test body'
        assert msg.from_email == 'sender@example.com'
        assert msg.to == ['recipient@example.com']
        assert msg.cc == []
        assert msg.bcc == []
        assert msg.reply_to == []
        assert msg.attachments == []
        assert msg.headers == {}
        assert msg.html_body is None

    def test_email_with_all_fields(self):
        """Test creating email with all optional fields."""
        attachment = Attachment(
            filename='test.txt',
            content=b'test content',
            mimetype='text/plain',
        )

        msg = EmailMessage(
            subject='Complete Email',
            body='Plain text body',
            html_body='<p>HTML body</p>',
            from_email='sender@example.com',
            to=['to1@example.com', 'to2@example.com'],
            cc=['cc@example.com'],
            bcc=['bcc@example.com'],
            reply_to=['reply@example.com'],
            attachments=[attachment],
            headers={'X-Custom': 'value'},
        )

        assert msg.subject == 'Complete Email'
        assert msg.html_body == '<p>HTML body</p>'
        assert len(msg.to) == 2
        assert msg.cc == ['cc@example.com']
        assert msg.bcc == ['bcc@example.com']
        assert msg.reply_to == ['reply@example.com']
        assert len(msg.attachments) == 1
        assert msg.headers == {'X-Custom': 'value'}

    def test_string_to_list_conversion(self):
        """Test that single string recipients are converted to lists."""
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to='single@example.com',
            cc='cc@example.com',
        )

        assert msg.to == ['single@example.com']
        assert msg.cc == ['cc@example.com']

    def test_email_validation(self):
        """Test email address validation."""
        # Valid email
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='valid@example.com',
            to=['recipient@example.com'],
        )
        assert msg.from_email == 'valid@example.com'

        # Invalid email should still work (EmailStr allows strings)
        msg2 = EmailMessage(
            subject='Test',
            body='Body',
            from_email='not-an-email',
            to=['recipient@example.com'],
        )
        assert msg2.from_email == 'not-an-email'

    def test_recipients_method(self):
        """Test recipients() method returns all recipients."""
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['to1@example.com', 'to2@example.com'],
            cc=['cc@example.com'],
            bcc=['bcc1@example.com', 'bcc2@example.com'],
        )

        recipients = msg.recipients()
        assert len(recipients) == 5
        assert 'to1@example.com' in recipients
        assert 'to2@example.com' in recipients
        assert 'cc@example.com' in recipients
        assert 'bcc1@example.com' in recipients
        assert 'bcc2@example.com' in recipients

    def test_as_mime_simple_text(self):
        """Test converting simple text email to MIME."""
        msg = EmailMessage(
            subject='Test Subject',
            body='Test body',
            from_email='sender@example.com',
            to=['recipient@example.com'],
        )

        mime = msg.as_mime()

        assert isinstance(mime, MIMEText)
        assert mime['Subject'] == 'Test Subject'
        assert mime['From'] == 'sender@example.com'
        assert mime['To'] == 'recipient@example.com'
        # Body is base64 encoded in MIME, so check payload
        payload = mime.get_payload(decode=True)
        assert isinstance(payload, bytes)
        assert payload.decode('utf-8') == 'Test body'

    def test_as_mime_with_html(self):
        """Test converting email with HTML to MIME."""
        msg = EmailMessage(
            subject='HTML Email',
            body='Plain text',
            html_body='<h1>HTML</h1>',
            from_email='sender@example.com',
            to=['recipient@example.com'],
        )

        mime = msg.as_mime()

        assert isinstance(mime, MIMEMultipart)
        assert mime['Subject'] == 'HTML Email'
        assert mime.is_multipart()

    def test_as_mime_with_attachments(self):
        """Test converting email with attachments to MIME."""
        attachment = Attachment(
            filename='test.pdf',
            content=b'PDF content',
            mimetype='application/pdf',
        )

        msg = EmailMessage(
            subject='With Attachment',
            body='See attachment',
            from_email='sender@example.com',
            to=['recipient@example.com'],
            attachments=[attachment],
        )

        mime = msg.as_mime()

        assert isinstance(mime, MIMEMultipart)
        assert mime.is_multipart()
        assert 'test.pdf' in mime.as_string()

    def test_as_mime_with_cc_and_reply_to(self):
        """Test MIME message includes CC and Reply-To headers."""
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['to@example.com'],
            cc=['cc@example.com'],
            reply_to=['reply@example.com'],
        )

        mime = msg.as_mime()

        assert mime['Cc'] == 'cc@example.com'
        assert mime['Reply-To'] == 'reply@example.com'

    def test_as_mime_with_custom_headers(self):
        """Test MIME message includes custom headers."""
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['to@example.com'],
            headers={'X-Custom-Header': 'CustomValue', 'X-Priority': '1'},
        )

        mime = msg.as_mime()

        assert mime['X-Custom-Header'] == 'CustomValue'
        assert mime['X-Priority'] == '1'

    def test_extra_fields_allowed(self):
        """Test that extra fields are allowed via config."""
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['to@example.com'],
            custom_field='custom_value',
        )

        assert msg.custom_field == 'custom_value'  # type: ignore[attr-defined]

    def test_tags_field(self):
        """Test tags field for message categorization."""
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['to@example.com'],
            tags=['marketing', 'newsletter', 'promotional'],
        )

        assert msg.tags == ['marketing', 'newsletter', 'promotional']
        assert len(msg.tags) == 3

    def test_tags_default_empty(self):
        """Test that tags defaults to empty list."""
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['to@example.com'],
        )

        assert msg.tags == []

    def test_metadata_field(self):
        """Test metadata field for custom tracking data."""
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['to@example.com'],
            metadata={
                'user_id': '12345',
                'campaign': 'summer_sale',
                'ab_test': 'variant_a',
            },
        )

        assert msg.metadata == {'user_id': '12345', 'campaign': 'summer_sale', 'ab_test': 'variant_a'}
        assert msg.metadata['user_id'] == '12345'

    def test_metadata_default_empty(self):
        """Test that metadata defaults to empty dict."""
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['to@example.com'],
        )

        assert msg.metadata == {}

    def test_tags_and_metadata_combined(self):
        """Test using both tags and metadata together."""
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['to@example.com'],
            tags=['welcome', 'onboarding'],
            metadata={'user_id': '999', 'source': 'web'},
        )

        assert msg.tags == ['welcome', 'onboarding']
        assert msg.metadata == {'user_id': '999', 'source': 'web'}

    def test_template_id_field(self):
        """Test template_id field for templated emails."""
        msg = EmailMessage(
            subject='Welcome!',
            from_email='sender@example.com',
            to=['to@example.com'],
            template_id='welcome-template',
        )

        assert msg.template_id == 'welcome-template'
        assert msg.body == ''

    def test_merge_data_field(self):
        """Test merge_data field for per-recipient template variables."""
        msg = EmailMessage(
            subject='Test',
            from_email='sender@example.com',
            to=['user1@example.com', 'user2@example.com'],
            template_id='test-template',
            merge_data={
                'user1@example.com': {'name': 'Alice', 'order_id': '123'},
                'user2@example.com': {'name': 'Bob', 'order_id': '456'},
            },
        )

        assert msg.merge_data is not None
        assert msg.merge_data['user1@example.com'] == {'name': 'Alice', 'order_id': '123'}
        assert msg.merge_data['user2@example.com'] == {'name': 'Bob', 'order_id': '456'}

    def test_merge_global_data_field(self):
        """Test merge_global_data field for global template variables."""
        msg = EmailMessage(
            subject='Test',
            from_email='sender@example.com',
            to=['to@example.com'],
            template_id='test-template',
            merge_global_data={'company': 'Acme Inc', 'year': '2024'},
        )

        assert msg.merge_global_data == {'company': 'Acme Inc', 'year': '2024'}

    def test_get_template_data_global_only(self):
        """Test get_template_data with only global data."""
        msg = EmailMessage(
            subject='Test',
            from_email='sender@example.com',
            to=['to@example.com'],
            template_id='test-template',
            merge_global_data={'company': 'Acme Inc', 'year': '2024'},
        )

        data = msg.get_template_data('to@example.com')
        assert data == {'company': 'Acme Inc', 'year': '2024'}

    def test_get_template_data_recipient_specific(self):
        """Test get_template_data with recipient-specific data."""
        msg = EmailMessage(
            subject='Test',
            from_email='sender@example.com',
            to=['user@example.com'],
            template_id='test-template',
            merge_data={
                'user@example.com': {'name': 'John', 'order_id': '789'},
            },
        )

        data = msg.get_template_data('user@example.com')
        assert data == {'name': 'John', 'order_id': '789'}

    def test_get_template_data_merged(self):
        """Test get_template_data merges global and recipient-specific data."""
        msg = EmailMessage(
            subject='Test',
            from_email='sender@example.com',
            to=['user@example.com'],
            template_id='test-template',
            merge_global_data={'company': 'Acme Inc', 'year': '2024'},
            merge_data={
                'user@example.com': {'name': 'John', 'order_id': '789'},
            },
        )

        data = msg.get_template_data('user@example.com')
        assert data == {'company': 'Acme Inc', 'year': '2024', 'name': 'John', 'order_id': '789'}

    def test_get_template_data_recipient_overrides_global(self):
        """Test that recipient-specific data overrides global data."""
        msg = EmailMessage(
            subject='Test',
            from_email='sender@example.com',
            to=['user@example.com'],
            template_id='test-template',
            merge_global_data={'name': 'Default Name', 'company': 'Acme Inc'},
            merge_data={
                'user@example.com': {'name': 'John'},
            },
        )

        data = msg.get_template_data('user@example.com')
        assert data == {'name': 'John', 'company': 'Acme Inc'}

    def test_body_optional_with_template(self):
        """Test that body is optional when template_id is provided."""
        msg = EmailMessage(
            subject='Test',
            from_email='sender@example.com',
            to=['to@example.com'],
            template_id='test-template',
        )

        assert msg.body == ''
        assert msg.template_id == 'test-template'

    def test_body_required_without_template(self):
        """Test that either body or template_id must be provided."""
        with pytest.raises(ValidationError, match='Either body or template_id must be provided'):
            EmailMessage(
                subject='Test',
                from_email='sender@example.com',
                to=['to@example.com'],
            )

    def test_track_opens_field(self):
        """Test track_opens field for open tracking."""
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['to@example.com'],
            track_opens=True,
        )

        assert msg.track_opens is True

    def test_track_opens_default_false(self):
        """Test that track_opens defaults to False."""
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['to@example.com'],
        )

        assert msg.track_opens is False

    def test_track_clicks_field(self):
        """Test track_clicks field for click tracking."""
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['to@example.com'],
            track_clicks=True,
        )

        assert msg.track_clicks is True

    def test_track_clicks_default_false(self):
        """Test that track_clicks defaults to False."""
        msg = EmailMessage(
            subject='Test',
            body='Body',
            from_email='sender@example.com',
            to=['to@example.com'],
        )

        assert msg.track_clicks is False

    def test_track_opens_and_clicks_combined(self):
        """Test using both tracking options together."""
        msg = EmailMessage(
            subject='Newsletter',
            body='Newsletter content',
            html_body='<h1>Newsletter</h1>',
            from_email='news@example.com',
            to=['subscriber@example.com'],
            track_opens=True,
            track_clicks=True,
        )

        assert msg.track_opens is True
        assert msg.track_clicks is True

    def test_as_mime_with_inline_image(self):
        """Test MIME structure with inline image attachment."""
        inline = Attachment(
            filename='logo.png',
            content=b'PNG data',
            mimetype='image/png',
            content_id='logo',
        )

        msg = EmailMessage(
            subject='Inline Test',
            body='See image',
            html_body='<img src="cid:logo">',
            from_email='sender@example.com',
            to=['to@example.com'],
            attachments=[inline],
        )

        mime = msg.as_mime()

        # Should be multipart/related (no regular attachments, so no mixed wrapper)
        assert isinstance(mime, MIMEMultipart)
        assert mime.get_content_subtype() == 'related'

        # First child should be multipart/alternative (body)
        parts: list[Message] = mime.get_payload()  # type: ignore[assignment]
        assert parts[0].get_content_subtype() == 'alternative'

        # Second child should be the inline image
        inline_part = parts[1]
        assert inline_part['Content-ID'] == '<logo>'
        disposition_params = inline_part.get_params(header='Content-Disposition')
        assert disposition_params is not None
        assert disposition_params[0] == ('inline', '')

    def test_as_mime_with_inline_and_regular_attachments(self):
        """Test MIME structure with both inline and regular attachments."""
        inline = Attachment(
            filename='logo.png',
            content=b'PNG data',
            mimetype='image/png',
            content_id='logo',
        )
        regular = Attachment(
            filename='report.pdf',
            content=b'PDF data',
            mimetype='application/pdf',
        )

        msg = EmailMessage(
            subject='Mixed Test',
            body='See attached',
            html_body='<img src="cid:logo">',
            from_email='sender@example.com',
            to=['to@example.com'],
            attachments=[inline, regular],
        )

        mime = msg.as_mime()

        # Top level should be multipart/mixed
        assert isinstance(mime, MIMEMultipart)
        assert mime.get_content_subtype() == 'mixed'

        top_parts: list[Message] = mime.get_payload()  # type: ignore[assignment]
        # First child: multipart/related (body + inline images)
        assert top_parts[0].get_content_subtype() == 'related'
        # Second child: regular attachment
        assert 'report.pdf' in str(top_parts[1]['Content-Disposition'])

        # Inside related: alternative + inline image
        related_parts: list[Message] = top_parts[0].get_payload()  # type: ignore[assignment]
        assert related_parts[0].get_content_subtype() == 'alternative'
        assert related_parts[1]['Content-ID'] == '<logo>'

    def test_as_mime_with_inline_image_no_html(self):
        """Test inline image with plain text only (no HTML body)."""
        inline = Attachment(
            filename='photo.jpg',
            content=b'JPEG data',
            mimetype='image/jpeg',
            content_id='photo',
        )

        msg = EmailMessage(
            subject='Plain + Inline',
            body='See image',
            from_email='sender@example.com',
            to=['to@example.com'],
            attachments=[inline],
        )

        mime = msg.as_mime()

        # Should still be multipart/related wrapping plain text + inline
        assert isinstance(mime, MIMEMultipart)
        assert mime.get_content_subtype() == 'related'

        parts: list[Message] = mime.get_payload()  # type: ignore[assignment]
        # First child is plain text
        assert parts[0].get_content_type() == 'text/plain'
        # Second child is inline image
        assert parts[1]['Content-ID'] == '<photo>'
