"""Tests for the receive_emails skill."""

import os
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from unittest.mock import MagicMock, patch

import pytest

from fliiq.runtime.package_data import bundled_skills_dir
from fliiq.runtime.skills.base import SkillBase

SKILLS_DIR = str(bundled_skills_dir())


def _load_skill() -> SkillBase:
    return SkillBase(os.path.join(SKILLS_DIR, "receive_emails"))


def _make_plain_email(uid: str, from_: str, subject: str, body: str) -> bytes:
    msg = MIMEText(body, "plain")
    msg["From"] = from_
    msg["Subject"] = subject
    msg["Date"] = "Mon, 10 Feb 2025 12:00:00 +0000"
    return msg.as_bytes()


def _make_html_email(uid: str, from_: str, subject: str, html_body: str) -> bytes:
    msg = MIMEText(html_body, "html")
    msg["From"] = from_
    msg["Subject"] = subject
    msg["Date"] = "Mon, 10 Feb 2025 12:00:00 +0000"
    return msg.as_bytes()


def _make_multipart_email(uid: str, from_: str, subject: str, plain: str, html: str) -> bytes:
    msg = MIMEMultipart("alternative")
    msg["From"] = from_
    msg["Subject"] = subject
    msg["Date"] = "Mon, 10 Feb 2025 12:00:00 +0000"
    msg.attach(MIMEText(plain, "plain"))
    msg.attach(MIMEText(html, "html"))
    return msg.as_bytes()


def _mock_imap(uid_list: list[bytes], messages: dict[str, bytes], seen_uids: set[str] | None = None):
    """Create a mock IMAP4_SSL connection.

    seen_uids: set of UID strings that should have \\Seen flag.
    """
    seen_uids = seen_uids or set()
    mock_conn = MagicMock()
    mock_conn.login.return_value = ("OK", [])
    mock_conn.select.return_value = ("OK", [b"1"])

    search_data = [b" ".join(uid_list)] if uid_list else [b""]
    mock_conn.uid.side_effect = lambda cmd, *args: _imap_uid_handler(
        cmd, args, search_data, messages, seen_uids
    )
    mock_conn.logout.return_value = ("OK", [])
    return mock_conn


def _imap_uid_handler(cmd, args, search_data, messages, seen_uids):
    if cmd == "search":
        return ("OK", search_data)
    elif cmd == "fetch":
        uid_str = args[0]
        raw = messages.get(uid_str, b"")
        flags = b"\\Seen" if uid_str in seen_uids else b""
        flags_line = b"%s (RFC822 {%d} FLAGS (%s))" % (uid_str.encode(), len(raw), flags)
        return ("OK", [(flags_line, raw)])
    return ("OK", [])


def test_receive_emails_schema():
    skill = _load_skill()
    schema = skill.schema()
    assert schema["name"] == "receive_emails"
    assert "max_results" in schema["parameters"]["properties"]
    assert "filter" in schema["parameters"]["properties"]


async def test_receive_emails_missing_gmail_address(monkeypatch):
    monkeypatch.delenv("GMAIL_ADDRESS", raising=False)
    monkeypatch.delenv("GMAIL_APP_PASSWORD", raising=False)
    skill = _load_skill()
    with pytest.raises(ValueError, match="GMAIL_ADDRESS"):
        await skill.execute({})


async def test_receive_emails_missing_app_password(monkeypatch):
    monkeypatch.setenv("GMAIL_ADDRESS", "test@gmail.com")
    monkeypatch.delenv("GMAIL_APP_PASSWORD", raising=False)
    skill = _load_skill()
    with pytest.raises(ValueError, match="GMAIL_APP_PASSWORD"):
        await skill.execute({})


async def test_receive_emails_empty_inbox(monkeypatch):
    monkeypatch.setenv("GMAIL_ADDRESS", "test@gmail.com")
    monkeypatch.setenv("GMAIL_APP_PASSWORD", "fake-password")
    skill = _load_skill()

    mock_conn = _mock_imap([], {})
    with patch("imaplib.IMAP4_SSL", return_value=mock_conn):
        result = await skill.execute({})

    assert result["emails"] == []
    assert result["has_more"] is False


async def test_receive_emails_success(monkeypatch):
    monkeypatch.setenv("GMAIL_ADDRESS", "test@gmail.com")
    monkeypatch.setenv("GMAIL_APP_PASSWORD", "fake-password")
    skill = _load_skill()

    raw1 = _make_plain_email("1", "alice@example.com", "Hello", "Hi there")
    raw2 = _make_plain_email("2", "bob@example.com", "Meeting", "At 3pm")
    mock_conn = _mock_imap([b"1", b"2"], {"1": raw1, "2": raw2})

    with patch("imaplib.IMAP4_SSL", return_value=mock_conn):
        result = await skill.execute({})

    assert len(result["emails"]) == 2
    # Newest first (reversed UIDs)
    assert result["emails"][0]["from"] == "bob@example.com"
    assert result["emails"][1]["from"] == "alice@example.com"
    assert result["has_more"] is False


async def test_receive_emails_html_fallback(monkeypatch):
    monkeypatch.setenv("GMAIL_ADDRESS", "test@gmail.com")
    monkeypatch.setenv("GMAIL_APP_PASSWORD", "fake-password")
    skill = _load_skill()

    raw = _make_html_email("1", "alice@example.com", "HTML", "<p>Bold <b>text</b></p>")
    mock_conn = _mock_imap([b"1"], {"1": raw})

    with patch("imaplib.IMAP4_SSL", return_value=mock_conn):
        result = await skill.execute({})

    assert len(result["emails"]) == 1
    body = result["emails"][0]["body"]
    assert "Bold" in body
    assert "text" in body
    assert "<p>" not in body
    assert "<b>" not in body


async def test_receive_emails_has_more(monkeypatch):
    monkeypatch.setenv("GMAIL_ADDRESS", "test@gmail.com")
    monkeypatch.setenv("GMAIL_APP_PASSWORD", "fake-password")
    skill = _load_skill()

    # 12 UIDs but max_results defaults to 10
    uids = [str(i).encode() for i in range(1, 13)]
    messages = {
        str(i): _make_plain_email(str(i), f"user{i}@example.com", f"Msg {i}", f"Body {i}")
        for i in range(1, 13)
    }
    mock_conn = _mock_imap(uids, messages)

    with patch("imaplib.IMAP4_SSL", return_value=mock_conn):
        result = await skill.execute({})

    assert len(result["emails"]) == 10
    assert result["has_more"] is True


async def test_receive_emails_read_field(monkeypatch):
    """Emails include read status."""
    monkeypatch.setenv("GMAIL_ADDRESS", "test@gmail.com")
    monkeypatch.setenv("GMAIL_APP_PASSWORD", "fake-password")
    skill = _load_skill()

    raw1 = _make_plain_email("1", "alice@example.com", "Unread", "body")
    raw2 = _make_plain_email("2", "bob@example.com", "Read", "body")
    mock_conn = _mock_imap([b"1", b"2"], {"1": raw1, "2": raw2}, seen_uids={"2"})

    with patch("imaplib.IMAP4_SSL", return_value=mock_conn):
        result = await skill.execute({})

    # Newest first: "2" (read) then "1" (unread)
    assert result["emails"][0]["read"] is True
    assert result["emails"][1]["read"] is False


async def test_receive_emails_filter_unread(monkeypatch):
    """filter='unread' passes UNSEEN to IMAP search."""
    monkeypatch.setenv("GMAIL_ADDRESS", "test@gmail.com")
    monkeypatch.setenv("GMAIL_APP_PASSWORD", "fake-password")
    skill = _load_skill()

    raw = _make_plain_email("1", "alice@example.com", "Hello", "Hi")
    mock_conn = _mock_imap([b"1"], {"1": raw})

    with patch("imaplib.IMAP4_SSL", return_value=mock_conn):
        result = await skill.execute({"filter": "unread"})

    assert len(result["emails"]) == 1
    # Verify UNSEEN was passed to search
    mock_conn.uid.assert_any_call("search", None, "UNSEEN")


async def test_receive_emails_filter_read(monkeypatch):
    """filter='read' passes SEEN to IMAP search."""
    monkeypatch.setenv("GMAIL_ADDRESS", "test@gmail.com")
    monkeypatch.setenv("GMAIL_APP_PASSWORD", "fake-password")
    skill = _load_skill()

    raw = _make_plain_email("1", "alice@example.com", "Hello", "Hi")
    mock_conn = _mock_imap([b"1"], {"1": raw}, seen_uids={"1"})

    with patch("imaplib.IMAP4_SSL", return_value=mock_conn):
        result = await skill.execute({"filter": "read"})

    assert len(result["emails"]) == 1
    mock_conn.uid.assert_any_call("search", None, "SEEN")


async def test_receive_emails_filter_invalid(monkeypatch):
    """Invalid filter raises ValueError."""
    monkeypatch.setenv("GMAIL_ADDRESS", "test@gmail.com")
    monkeypatch.setenv("GMAIL_APP_PASSWORD", "fake-password")
    skill = _load_skill()

    with pytest.raises(ValueError, match="Invalid filter"):
        await skill.execute({"filter": "starred"})


async def test_receive_emails_env_override(monkeypatch):
    """Env var override: FLIIQ_GMAIL_* env vars configure credentials."""
    monkeypatch.setenv("FLIIQ_GMAIL_ADDRESS", "work@gmail.com")
    monkeypatch.setenv("FLIIQ_GMAIL_APP_PASSWORD", "work-pass")
    skill = _load_skill()

    raw = _make_plain_email("1", "alice@example.com", "Hello", "Hi there")
    mock_conn = _mock_imap([b"1"], {"1": raw})

    with patch("imaplib.IMAP4_SSL", return_value=mock_conn):
        result = await skill.execute({})

    assert len(result["emails"]) == 1
    mock_conn.login.assert_called_once_with("work@gmail.com", "work-pass")
