"""Tests for email service (T05)."""
import pytest
from unittest.mock import AsyncMock, patch, MagicMock
from email.mime.multipart import MIMEMultipart

from app.services.email_service import EmailService


@pytest.mark.asyncio
async def test_send_contact_email_success():
    """send_contact_email sends correct email via aiosmtplib."""
    service = EmailService()
    contact_data = {
        "name": "Test User",
        "email": "test@example.com",
        "phone": "+49 123 456",
        "message": "Hallo Welt",
    }
    with patch("app.services.email_service.aiosmtplib.send", new_callable=AsyncMock) as mock_send:
        result = await service.send_contact_email(contact_data)
    assert result is True
    mock_send.assert_awaited_once()
    sent_msg = mock_send.call_args[0][0]
    assert isinstance(sent_msg, MIMEMultipart)
    assert "Hallo Welt" in str(sent_msg)
    assert "Test User" in str(sent_msg)


@pytest.mark.asyncio
async def test_send_rental_confirmation_success():
    """send_rental_confirmation sends email with reference_number and items."""
    service = EmailService()
    items = [
        {"equipment_name": "L-Acoustics K2", "quantity": 2},
        {"equipment_name": "d&b T10", "quantity": 4},
    ]
    with patch("app.services.email_service.aiosmtplib.send", new_callable=AsyncMock) as mock_send:
        result = await service.send_rental_confirmation(
            to_addr="customer@example.com",
            reference_number="HMS-2026-00042",
            event_name="Rock Festival",
            items=items,
        )
    assert result is True
    mock_send.assert_awaited_once()
    sent_msg = mock_send.call_args[0][0]
    msg_str = str(sent_msg)
    assert "HMS-2026-00042" in msg_str
    assert "Rock Festival" in msg_str
    assert "L-Acoustics K2" in msg_str
    assert "d&b T10" in msg_str


@pytest.mark.asyncio
async def test_send_email_smtp_failure_returns_false():
    """SMTP failure returns False, no crash, graceful handling."""
    service = EmailService()
    with patch("app.services.email_service.aiosmtplib.send", new_callable=AsyncMock, side_effect=Exception("SMTP refused")):
        result = await service.send_email(
            to_addr="fail@example.com",
            subject="Test Subject",
            html_body="<p>Test</p>",
            text_body="Test",
        )
    assert result is False


@pytest.mark.asyncio
async def test_send_contact_email_smtp_failure_returns_false():
    """send_contact_email returns False on SMTP failure without crashing."""
    service = EmailService()
    with patch("app.services.email_service.aiosmtplib.send", new_callable=AsyncMock, side_effect=Exception("Connection refused")):
        result = await service.send_contact_email({
            "name": "Fail User",
            "email": "fail@example.com",
            "phone": "+49 000",
            "message": "Soll fehlschlagen",
        })
    assert result is False


@pytest.mark.asyncio
async def test_send_rental_confirmation_smtp_failure_returns_false():
    """send_rental_confirmation returns False on SMTP failure without crashing."""
    service = EmailService()
    with patch("app.services.email_service.aiosmtplib.send", new_callable=AsyncMock, side_effect=Exception("Timeout")):
        result = await service.send_rental_confirmation(
            to_addr="fail@example.com",
            reference_number="HMS-2026-99999",
            event_name="Fail Event",
            items=[{"equipment_name": "Speaker", "quantity": 1}],
        )
    assert result is False


@pytest.mark.asyncio
async def test_send_email_failure_saves_to_queue():
    """send_email saves failed email to email_queue when db is provided."""
    from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
    from app.database import Base
    from app.models.email_queue import EmailQueue
    from sqlalchemy import select

    engine = create_async_engine("sqlite+aiosqlite:///:memory:")
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    session_maker = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

    service = EmailService()
    with patch("app.services.email_service.aiosmtplib.send", new_callable=AsyncMock, side_effect=Exception("SMTP down")):
        async with session_maker() as db:
            result = await service.send_email(
                to_addr="queue@example.com",
                subject="Queued Email",
                html_body="<p>Body</p>",
                text_body="Body",
                reply_to="reply@example.com",
                db=db,
            )
    assert result is False

    async with session_maker() as db:
        queued = await db.execute(select(EmailQueue).where(EmailQueue.to_addr == "queue@example.com"))
        item = queued.scalar_one_or_none()
        assert item is not None
        assert item.subject == "Queued Email"
        assert item.status == "pending"
        assert item.attempts == 0
        assert item.reply_to == "reply@example.com"

    await engine.dispose()


@pytest.mark.asyncio
async def test_retry_failed_emails_resends_pending():
    """retry_failed_emails retries pending queue items and marks sent on success."""
    from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
    from app.database import Base
    from app.models.email_queue import EmailQueue
    from sqlalchemy import select

    engine = create_async_engine("sqlite+aiosqlite:///:memory:")
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    session_maker = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

    async with session_maker() as db:
        db.add(EmailQueue(
            to_addr="retry@example.com",
            subject="Retry Test",
            html_body="<p>Retry</p>",
            text_body="Retry",
            status="pending",
            attempts=0,
        ))
        await db.commit()

    with patch("app.services.email_service.aiosmtplib.send", new_callable=AsyncMock):
        async with session_maker() as db:
            sent_count = await EmailService.retry_failed_emails(db)
    assert sent_count == 1

    async with session_maker() as db:
        result = await db.execute(select(EmailQueue).where(EmailQueue.to_addr == "retry@example.com"))
        item = result.scalar_one_or_none()
        assert item.status == "sent"
        assert item.attempts == 1

    await engine.dispose()
