"""Tests for Read Emails node."""

import base64
from email.message import EmailMessage
from unittest.mock import MagicMock, patch

import pytest

from fw_nodes_email.nodes.read_emails import ReadEmailsNode


@pytest.fixture
def read_emails_node():
    return ReadEmailsNode()


@pytest.fixture
def mock_context_with_imap(mock_context):
    """Mock context with IMAP credentials."""
    mock_context._credentials = {
        "cred_imap_123": {
            "host": "imap.example.com",
            "port": 993,
            "username": "user@example.com",
            "password": "imap_password_xyz",
            "use_ssl": True,
        }
    }
    return mock_context


@pytest.fixture
def basic_read_inputs():
    return {
        "credential_id": "cred_imap_123",
        "folder": "INBOX",
        "filter": "ALL",
        "max_results": 5,
        "mark_as_read": False,
    }


def _build_raw_email(
    subject: str = "Test Subject",
    from_addr: str = "sender@example.com",
    to_addr: str = "recipient@example.com",
    body: str = "Hello, this is a test email body.",
    message_id: str = "<test123@example.com>",
) -> bytes:
    """Build a raw email message as bytes for IMAP mock."""
    msg = EmailMessage()
    msg["Subject"] = subject
    msg["From"] = from_addr
    msg["To"] = to_addr
    msg["Message-ID"] = message_id
    msg["Date"] = "Mon, 01 Jan 2025 12:00:00 +0000"
    msg.set_content(body)
    return msg.as_bytes()


def _build_raw_email_with_attachment(
    subject: str = "Email with Attachment",
    body: str = "See attached.",
    filename: str = "test.txt",
    file_content: bytes = b"attachment content here",
) -> bytes:
    """Build a raw email with an attachment."""
    msg = EmailMessage()
    msg["Subject"] = subject
    msg["From"] = "sender@example.com"
    msg["To"] = "recipient@example.com"
    msg["Message-ID"] = "<attach123@example.com>"
    msg["Date"] = "Mon, 01 Jan 2025 12:00:00 +0000"
    msg.set_content(body)
    msg.add_attachment(
        file_content,
        maintype="application",
        subtype="octet-stream",
        filename=filename,
    )
    return msg.as_bytes()


def _mock_imap(search_ids: bytes = b"1 2 3", raw_email: bytes | None = None):
    """Create a mock IMAP4_SSL instance."""
    if raw_email is None:
        raw_email = _build_raw_email()
    mock = MagicMock()
    mock.login.return_value = ("OK", [b"LOGIN completed"])
    mock.select.return_value = ("OK", [b"3"])
    mock.search.return_value = ("OK", [search_ids])
    mock.fetch.return_value = ("OK", [(b"1 (RFC822 {1234})", raw_email)])
    mock.store.return_value = ("OK", [b"1 (FLAGS (\\Seen))"])
    mock.close.return_value = ("OK", [b"CLOSE completed"])
    mock.logout.return_value = ("BYE", [b"Logging out"])
    return mock


@pytest.mark.asyncio
async def test_read_emails_success(read_emails_node, mock_context_with_imap, basic_read_inputs):
    """Test successful email reading."""
    mock = _mock_imap()

    with patch("fw_nodes_email.nodes.read_emails.imaplib.IMAP4_SSL", return_value=mock):
        result = await read_emails_node.execute(basic_read_inputs, mock_context_with_imap)

    assert result.count > 0
    assert result.folder == "INBOX"
    assert len(result.emails) > 0
    assert result.emails[0]["subject"] == "Test Subject"
    assert result.emails[0]["from"] == "sender@example.com"


@pytest.mark.asyncio
async def test_read_emails_empty_inbox(read_emails_node, mock_context_with_imap, basic_read_inputs):
    """Test reading from an empty inbox."""
    mock = _mock_imap(search_ids=b"")

    with patch("fw_nodes_email.nodes.read_emails.imaplib.IMAP4_SSL", return_value=mock):
        result = await read_emails_node.execute(basic_read_inputs, mock_context_with_imap)

    assert result.count == 0
    assert result.emails == []
    assert result.folder == "INBOX"


@pytest.mark.asyncio
async def test_read_emails_unseen_filter(read_emails_node, mock_context_with_imap):
    """Test filtering for unseen emails."""
    inputs = {
        "credential_id": "cred_imap_123",
        "folder": "INBOX",
        "filter": "UNSEEN",
        "max_results": 10,
        "mark_as_read": False,
    }

    raw_email = _build_raw_email(subject="Unread Email")
    mock = _mock_imap(search_ids=b"5", raw_email=raw_email)

    with patch("fw_nodes_email.nodes.read_emails.imaplib.IMAP4_SSL", return_value=mock):
        result = await read_emails_node.execute(inputs, mock_context_with_imap)

    mock.search.assert_called_once_with(None, "UNSEEN")
    assert result.count == 1
    assert result.emails[0]["subject"] == "Unread Email"


@pytest.mark.asyncio
async def test_read_emails_mark_as_read(read_emails_node, mock_context_with_imap):
    """Test marking emails as read."""
    inputs = {
        "credential_id": "cred_imap_123",
        "folder": "INBOX",
        "filter": "ALL",
        "max_results": 5,
        "mark_as_read": True,
    }

    mock = _mock_imap(search_ids=b"1")

    with patch("fw_nodes_email.nodes.read_emails.imaplib.IMAP4_SSL", return_value=mock):
        result = await read_emails_node.execute(inputs, mock_context_with_imap)

    mock.select.assert_called_once_with("INBOX", readonly=False)
    mock.store.assert_called_once_with(b"1", "+FLAGS", "\\Seen")
    assert result.count == 1


@pytest.mark.asyncio
async def test_read_emails_readonly_when_not_marking(read_emails_node, mock_context_with_imap, basic_read_inputs):
    """Test mailbox opened as readonly when mark_as_read is False."""
    mock = _mock_imap(search_ids=b"")

    with patch("fw_nodes_email.nodes.read_emails.imaplib.IMAP4_SSL", return_value=mock):
        await read_emails_node.execute(basic_read_inputs, mock_context_with_imap)

    mock.select.assert_called_once_with("INBOX", readonly=True)


@pytest.mark.asyncio
async def test_read_emails_registers_password_as_secret(read_emails_node, mock_context_with_imap, basic_read_inputs):
    """Test that the IMAP password is registered as a secret."""
    mock = _mock_imap(search_ids=b"")

    with patch("fw_nodes_email.nodes.read_emails.imaplib.IMAP4_SSL", return_value=mock):
        await read_emails_node.execute(basic_read_inputs, mock_context_with_imap)

    assert "imap_password_xyz" in mock_context_with_imap.get_secrets()


@pytest.mark.asyncio
async def test_read_emails_credential_not_found(read_emails_node, mock_context):
    """Test error when credential is not found."""
    inputs = {
        "credential_id": "nonexistent",
        "folder": "INBOX",
        "filter": "ALL",
        "max_results": 5,
        "mark_as_read": False,
    }

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


@pytest.mark.asyncio
async def test_read_emails_custom_folder(read_emails_node, mock_context_with_imap):
    """Test reading from a custom folder."""
    inputs = {
        "credential_id": "cred_imap_123",
        "folder": "Sent",
        "filter": "ALL",
        "max_results": 5,
        "mark_as_read": False,
    }

    mock = _mock_imap(search_ids=b"")

    with patch("fw_nodes_email.nodes.read_emails.imaplib.IMAP4_SSL", return_value=mock):
        result = await read_emails_node.execute(inputs, mock_context_with_imap)

    mock.select.assert_called_once_with("Sent", readonly=True)
    assert result.folder == "Sent"


@pytest.mark.asyncio
async def test_read_emails_max_results_limit(read_emails_node, mock_context_with_imap):
    """Test that max_results limits the number of emails returned."""
    inputs = {
        "credential_id": "cred_imap_123",
        "folder": "INBOX",
        "filter": "ALL",
        "max_results": 2,
        "mark_as_read": False,
    }

    mock = _mock_imap(search_ids=b"1 2 3 4 5 6 7 8 9 10")

    with patch("fw_nodes_email.nodes.read_emails.imaplib.IMAP4_SSL", return_value=mock):
        await read_emails_node.execute(inputs, mock_context_with_imap)

    # Should only fetch 2 emails (the last 2 IDs: 9 and 10)
    assert mock.fetch.call_count == 2


@pytest.mark.asyncio
async def test_read_emails_with_attachment_metadata(read_emails_node, mock_context_with_imap):
    """Test that attachment metadata is returned."""
    raw_email = _build_raw_email_with_attachment()
    mock = _mock_imap(search_ids=b"1", raw_email=raw_email)

    inputs = {
        "credential_id": "cred_imap_123",
        "folder": "INBOX",
        "filter": "ALL",
        "max_results": 5,
        "mark_as_read": False,
    }

    with patch("fw_nodes_email.nodes.read_emails.imaplib.IMAP4_SSL", return_value=mock):
        result = await read_emails_node.execute(inputs, mock_context_with_imap)

    assert result.count == 1
    attachments = result.emails[0]["attachments"]
    assert len(attachments) == 1
    assert attachments[0]["filename"] == "test.txt"
    assert attachments[0]["size"] > 0
    # By default, content_base64 is NOT included
    assert "content_base64" not in attachments[0]


@pytest.mark.asyncio
async def test_read_emails_with_attachment_content(read_emails_node, mock_context_with_imap):
    """Test that attachment base64 content is included when requested."""
    file_content = b"secret attachment data"
    raw_email = _build_raw_email_with_attachment(file_content=file_content)
    mock = _mock_imap(search_ids=b"1", raw_email=raw_email)

    inputs = {
        "credential_id": "cred_imap_123",
        "folder": "INBOX",
        "filter": "ALL",
        "max_results": 5,
        "mark_as_read": False,
        "include_attachment_content": True,
    }

    with patch("fw_nodes_email.nodes.read_emails.imaplib.IMAP4_SSL", return_value=mock):
        result = await read_emails_node.execute(inputs, mock_context_with_imap)

    attachments = result.emails[0]["attachments"]
    assert len(attachments) == 1
    assert "content_base64" in attachments[0]
    decoded = base64.b64decode(attachments[0]["content_base64"])
    assert decoded == file_content


@pytest.mark.asyncio
async def test_read_emails_readonly_credential_rejects_mark_as_read(read_emails_node, mock_context):
    """Test that mark_as_read=True with readonly credential raises an error."""
    mock_context._credentials = {
        "cred_imap_readonly": {
            "host": "imap.example.com",
            "port": 993,
            "username": "user@example.com",
            "password": "imap_password_xyz",
            "use_ssl": True,
            "readonly": True,
        }
    }

    inputs = {
        "credential_id": "cred_imap_readonly",
        "folder": "INBOX",
        "filter": "ALL",
        "max_results": 5,
        "mark_as_read": True,
    }

    with pytest.raises(ValueError, match="credential is configured as readonly"):
        await read_emails_node.execute(inputs, mock_context)
