"""Тесты SMTP-бэкенда email (`services.email`).

Маппинг ошибок aiosmtplib в
`EmailSendError(retryable=...)`, вложение `.ics` в MIME, console-бэкенд по
умолчанию. `_env_file=None` в конструкторе `Settings` — тесты не должны
зависеть от содержимого реального `backend/.env` (изолированный конфиг).
"""

import logging
from email.message import EmailMessage
from typing import cast

import aiosmtplib
import pytest

from core.config import Settings
from services.email import (
    ConsoleEmailBackend,
    EmailAttachment,
    EmailSendError,
    SmtpEmailBackend,
    _build_message,
    create_email_backend,
)


def test_create_email_backend_defaults_to_console() -> None:
    backend = create_email_backend(Settings(_env_file=None))

    assert isinstance(backend, ConsoleEmailBackend)


def test_create_email_backend_smtp() -> None:
    backend = create_email_backend(
        Settings(
            _env_file=None,
            email_backend="smtp",
            smtp_host="smtp.example.com",
            smtp_port=2525,
        )
    )

    assert isinstance(backend, SmtpEmailBackend)


async def test_console_backend_logs_message(caplog: pytest.LogCaptureFixture) -> None:
    backend = ConsoleEmailBackend()

    with caplog.at_level(logging.INFO):
        await backend.send(
            to="user@example.com",
            subject="Тестовая тема",
            body="Тестовое тело письма",
            attachments=[
                EmailAttachment(filename="invite.ics", content=b"BEGIN", mime_type="text/calendar")
            ],
        )

    assert "user@example.com" in caplog.text
    assert "invite.ics" in caplog.text


def test_build_message_includes_html_alternative_and_ics_attachment() -> None:
    message = _build_message(
        sender="VidConf <no-reply@vidconf.example>",
        to="user@example.com",
        subject="Саммари встречи",
        body="Текстовая версия",
        html_body="<p>HTML-версия</p>",
        attachments=[
            EmailAttachment(
                filename="invite.ics",
                content=b"BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n",
                mime_type="text/calendar; method=REQUEST",
            )
        ],
    )

    assert message["Subject"] == "Саммари встречи"
    plain_part = message.get_body(preferencelist=("plain",))
    assert plain_part is not None
    assert "Текстовая версия" in plain_part.get_content()

    html_part = message.get_body(preferencelist=("html",))
    assert html_part is not None
    assert "HTML-версия" in html_part.get_content()

    attachments = list(message.iter_attachments())
    assert len(attachments) == 1
    assert attachments[0].get_filename() == "invite.ics"
    assert attachments[0].get_content_type() == "text/calendar"
    assert attachments[0].get_payload(decode=True) == b"BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n"


def test_build_message_sets_reply_to_when_given() -> None:
    message = _build_message(
        sender="VidConf <no-reply@vidconf.example>",
        to="user@example.com",
        subject="Тема",
        body="Тело",
        html_body=None,
        attachments=(),
        reply_to="contact@vidconf.example",
    )

    assert message["Reply-To"] == "contact@vidconf.example"


def test_build_message_omits_reply_to_when_not_given() -> None:
    message = _build_message(
        sender="VidConf <no-reply@vidconf.example>",
        to="user@example.com",
        subject="Тема",
        body="Тело",
        html_body=None,
        attachments=(),
    )

    assert message["Reply-To"] is None


def _backend() -> SmtpEmailBackend:
    return SmtpEmailBackend(
        hostname="smtp.example.com",
        port=587,
        username=None,
        password=None,
        start_tls=True,
        use_tls=False,
        timeout=30,
        sender="VidConf <no-reply@vidconf.example>",
    )


@pytest.mark.parametrize(
    "error",
    [
        aiosmtplib.SMTPConnectError("не удалось подключиться"),
        aiosmtplib.SMTPServerDisconnected("сервер оборвал соединение"),
        aiosmtplib.SMTPTimeoutError("таймаут"),
        aiosmtplib.SMTPAuthenticationError(535, "неверные учётные данные"),
    ],
)
async def test_smtp_backend_maps_transport_errors_to_retryable(
    monkeypatch: pytest.MonkeyPatch, error: Exception
) -> None:
    async def _raise(*args: object, **kwargs: object) -> None:
        raise error

    monkeypatch.setattr(aiosmtplib, "send", _raise)
    backend = _backend()

    with pytest.raises(EmailSendError) as excinfo:
        await backend.send(to="user@example.com", subject="Тема", body="Тело")

    assert excinfo.value.retryable is True


async def test_smtp_backend_maps_recipients_refused_to_non_retryable(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    refused = aiosmtplib.SMTPRecipientsRefused(
        [aiosmtplib.SMTPRecipientRefused(550, "мусор", "user@example.com")]
    )

    async def _raise(*args: object, **kwargs: object) -> None:
        raise refused

    monkeypatch.setattr(aiosmtplib, "send", _raise)
    backend = _backend()

    with pytest.raises(EmailSendError) as excinfo:
        await backend.send(to="user@example.com", subject="Тема", body="Тело")

    assert excinfo.value.retryable is False


async def test_smtp_backend_send_success_calls_aiosmtplib(monkeypatch: pytest.MonkeyPatch) -> None:
    captured: dict[str, object] = {}

    async def _fake_send(message: object, **kwargs: object) -> tuple[dict[str, object], str]:
        captured["message"] = message
        captured["kwargs"] = kwargs
        return {}, "OK"

    monkeypatch.setattr(aiosmtplib, "send", _fake_send)
    backend = _backend()

    await backend.send(to="user@example.com", subject="Тема", body="Тело")

    assert captured["kwargs"] == {
        "hostname": "smtp.example.com",
        "port": 587,
        "username": None,
        "password": None,
        "start_tls": True,
        "use_tls": False,
        "timeout": 30,
    }


async def test_smtp_backend_send_passes_reply_to_into_message(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    captured: dict[str, object] = {}

    async def _fake_send(message: object, **kwargs: object) -> tuple[dict[str, object], str]:
        captured["message"] = message
        return {}, "OK"

    monkeypatch.setattr(aiosmtplib, "send", _fake_send)
    backend = _backend()

    await backend.send(
        to="user@example.com", subject="Тема", body="Тело", reply_to="contact@vidconf.example"
    )

    message = cast(EmailMessage, captured["message"])
    assert message["Reply-To"] == "contact@vidconf.example"
