"""Tests for Send Email node."""

import base64
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from fw_nodes_email.nodes.send_email import SendEmailNode


@pytest.fixture
def send_email_node():
    return SendEmailNode()


@pytest.fixture
def mock_context_with_smtp(mock_context):
    """Mock context with SMTP credentials."""
    mock_context._credentials = {
        "cred_smtp_123": {
            "host": "smtp.example.com",
            "port": 587,
            "username": "user@example.com",
            "password": "smtp_password_xyz",
            "use_tls": True,
            "use_ssl": False,
        }
    }
    return mock_context


@pytest.fixture
def basic_email_inputs():
    return {
        "credential_id": "cred_smtp_123",
        "from_address": "sender@example.com",
        "to": "recipient@example.com",
        "subject": "Test Email",
        "body": "Hello, this is a test email.",
    }


def _mock_smtp():
    """Create a mock SMTP instance with all async methods."""
    mock = MagicMock()
    mock.connect = AsyncMock()
    mock.starttls = AsyncMock()
    mock.login = AsyncMock()
    mock.send_message = AsyncMock()
    mock.quit = AsyncMock()
    return mock


@pytest.mark.asyncio
async def test_send_email_success(send_email_node, mock_context_with_smtp, basic_email_inputs):
    """Test successful email sending."""
    mock_smtp_instance = _mock_smtp()

    with patch("fw_nodes_email.nodes.send_email.aiosmtplib.SMTP", return_value=mock_smtp_instance):
        result = await send_email_node.execute(basic_email_inputs, mock_context_with_smtp)

    assert result.success is True
    assert result.recipients == ["recipient@example.com"]
    mock_smtp_instance.connect.assert_called_once()
    mock_smtp_instance.login.assert_called_once_with("user@example.com", "smtp_password_xyz")
    mock_smtp_instance.send_message.assert_called_once()
    mock_smtp_instance.quit.assert_called_once()


@pytest.mark.asyncio
async def test_send_email_with_cc_and_bcc(send_email_node, mock_context_with_smtp, basic_email_inputs):
    """Test sending with CC and BCC recipients."""
    basic_email_inputs["cc"] = "cc1@example.com, cc2@example.com"
    basic_email_inputs["bcc"] = "bcc@example.com"

    mock_smtp_instance = _mock_smtp()

    with patch("fw_nodes_email.nodes.send_email.aiosmtplib.SMTP", return_value=mock_smtp_instance):
        result = await send_email_node.execute(basic_email_inputs, mock_context_with_smtp)

    assert result.success is True
    assert len(result.recipients) == 4
    assert "recipient@example.com" in result.recipients
    assert "cc1@example.com" in result.recipients
    assert "cc2@example.com" in result.recipients
    assert "bcc@example.com" in result.recipients


@pytest.mark.asyncio
async def test_send_html_email(send_email_node, mock_context_with_smtp, basic_email_inputs):
    """Test sending HTML email."""
    basic_email_inputs["is_html"] = True
    basic_email_inputs["body"] = "<h1>Hello</h1><p>This is HTML.</p>"

    mock_smtp_instance = _mock_smtp()

    with patch("fw_nodes_email.nodes.send_email.aiosmtplib.SMTP", return_value=mock_smtp_instance):
        result = await send_email_node.execute(basic_email_inputs, mock_context_with_smtp)

    assert result.success is True
    sent_msg = mock_smtp_instance.send_message.call_args[0][0]
    assert sent_msg.get_content_type() == "text/html"


@pytest.mark.asyncio
async def test_send_email_with_attachments(send_email_node, mock_context_with_smtp, basic_email_inputs):
    """Test sending email with file attachments."""
    file_content = b"Hello from attachment!"
    basic_email_inputs["attachments"] = [
        {
            "filename": "test.txt",
            "content_base64": base64.b64encode(file_content).decode("ascii"),
            "content_type": "text/plain",
        },
        {
            "filename": "data.bin",
            "content_base64": base64.b64encode(b"\x00\x01\x02").decode("ascii"),
            "content_type": "application/octet-stream",
        },
    ]

    mock_smtp_instance = _mock_smtp()

    with patch("fw_nodes_email.nodes.send_email.aiosmtplib.SMTP", return_value=mock_smtp_instance):
        result = await send_email_node.execute(basic_email_inputs, mock_context_with_smtp)

    assert result.success is True
    # Verify the message was sent with attachments
    sent_msg = mock_smtp_instance.send_message.call_args[0][0]
    assert sent_msg.is_multipart()

    # Count attachment parts
    attachment_parts = [
        part
        for part in sent_msg.walk()
        if part.get("Content-Disposition") and "attachment" in str(part.get("Content-Disposition"))
    ]
    assert len(attachment_parts) == 2


@pytest.mark.asyncio
async def test_send_email_ssl(send_email_node, mock_context):
    """Test sending via implicit SSL (port 465)."""
    mock_context._credentials = {
        "cred_smtp_ssl": {
            "host": "smtp.example.com",
            "port": 465,
            "username": "user@example.com",
            "password": "smtp_password_xyz",
            "use_tls": False,
            "use_ssl": True,
        }
    }

    inputs = {
        "credential_id": "cred_smtp_ssl",
        "from_address": "sender@example.com",
        "to": "recipient@example.com",
        "subject": "SSL Test",
        "body": "Sent via SSL.",
    }

    mock_smtp_instance = _mock_smtp()

    with patch("fw_nodes_email.nodes.send_email.aiosmtplib.SMTP", return_value=mock_smtp_instance) as mock_smtp_cls:
        result = await send_email_node.execute(inputs, mock_context)

    assert result.success is True
    mock_smtp_cls.assert_called_once_with(
        hostname="smtp.example.com",
        port=465,
        use_tls=True,
        start_tls=False,
        timeout=30,
    )


@pytest.mark.asyncio
async def test_send_email_registers_password_as_secret(send_email_node, mock_context_with_smtp, basic_email_inputs):
    """Test that the SMTP password is registered as a secret."""
    mock_smtp_instance = _mock_smtp()

    with patch("fw_nodes_email.nodes.send_email.aiosmtplib.SMTP", return_value=mock_smtp_instance):
        await send_email_node.execute(basic_email_inputs, mock_context_with_smtp)

    assert "smtp_password_xyz" in mock_context_with_smtp.get_secrets()


@pytest.mark.asyncio
async def test_send_email_credential_not_found(send_email_node, mock_context):
    """Test error when credential is not found."""
    inputs = {
        "credential_id": "nonexistent",
        "from_address": "sender@example.com",
        "to": "recipient@example.com",
        "subject": "Test",
        "body": "Test body",
    }

    with pytest.raises(ValueError, match="Credential nonexistent not found"):
        await send_email_node.execute(inputs, mock_context)


@pytest.mark.asyncio
async def test_send_email_smtp_error(send_email_node, mock_context_with_smtp, basic_email_inputs):
    """Test handling of SMTP errors."""
    import aiosmtplib

    mock_smtp_instance = _mock_smtp()
    mock_smtp_instance.login = AsyncMock(side_effect=aiosmtplib.SMTPAuthenticationError(535, "Authentication failed"))

    with (
        patch("fw_nodes_email.nodes.send_email.aiosmtplib.SMTP", return_value=mock_smtp_instance),
        pytest.raises(RuntimeError, match="SMTP error"),
    ):
        await send_email_node.execute(basic_email_inputs, mock_context_with_smtp)

    # quit should still be called in finally block
    mock_smtp_instance.quit.assert_called_once()


@pytest.mark.asyncio
async def test_send_email_multiple_recipients(send_email_node, mock_context_with_smtp):
    """Test sending to multiple comma-separated recipients."""
    inputs = {
        "credential_id": "cred_smtp_123",
        "from_address": "sender@example.com",
        "to": "user1@example.com, user2@example.com, user3@example.com",
        "subject": "Multi-recipient Test",
        "body": "Hello everyone!",
    }

    mock_smtp_instance = _mock_smtp()

    with patch("fw_nodes_email.nodes.send_email.aiosmtplib.SMTP", return_value=mock_smtp_instance):
        result = await send_email_node.execute(inputs, mock_context_with_smtp)

    assert result.success is True
    assert len(result.recipients) == 3
    assert "user1@example.com" in result.recipients
    assert "user2@example.com" in result.recipients
    assert "user3@example.com" in result.recipients


@pytest.mark.asyncio
async def test_send_email_attachment_too_large(send_email_node, mock_context_with_smtp):
    """Test that oversized attachments are rejected."""
    large_b64 = base64.b64encode(b"x" * (25 * 1024 * 1024 + 1)).decode("ascii")
    inputs = {
        "credential_id": "cred_smtp_123",
        "from_address": "sender@example.com",
        "to": "recipient@example.com",
        "subject": "Large Attachment",
        "body": "See attached.",
        "attachments": [
            {
                "filename": "huge.bin",
                "content_base64": large_b64,
                "content_type": "application/octet-stream",
            }
        ],
    }

    mock_smtp_instance = _mock_smtp()

    with (
        patch("fw_nodes_email.nodes.send_email.aiosmtplib.SMTP", return_value=mock_smtp_instance),
        pytest.raises(ValueError, match="exceeds maximum"),
    ):
        await send_email_node.execute(inputs, mock_context_with_smtp)


@pytest.mark.asyncio
async def test_send_email_generates_message_id(send_email_node, mock_context_with_smtp, basic_email_inputs):
    """Test that a Message-ID is always generated."""
    mock_smtp_instance = _mock_smtp()

    with patch("fw_nodes_email.nodes.send_email.aiosmtplib.SMTP", return_value=mock_smtp_instance):
        result = await send_email_node.execute(basic_email_inputs, mock_context_with_smtp)

    assert result.message_id != ""
    assert "@" in result.message_id  # Message-IDs contain @ sign
