"""Cloudflare Email Sending backend tests.

Focused on the REST-vs-Workers field-name differences (which fail as opaque
400s) and on retry/error classification. No network: urlopen is patched.
"""

import base64
import json
from unittest.mock import patch

import pytest
from django.core.mail import EmailMultiAlternatives, send_mail
from django.test import override_settings

CF_SETTINGS = dict(
    EMAIL_BACKEND="apps.core.email_backends.CloudflareEmailBackend",
    CLOUDFLARE_ACCOUNT_ID="acct123",
    CLOUDFLARE_EMAIL_API_TOKEN="token123",
    DEFAULT_FROM_EMAIL="no-reply@nocthink.com",
    CLOUDFLARE_EMAIL_MAX_RETRIES=2,
)


class FakeResponse:
    def __init__(self, payload):
        self._payload = json.dumps(payload).encode()

    def read(self):
        return self._payload

    def __enter__(self):
        return self

    def __exit__(self, *exc):
        return False


OK = {"success": True, "result": {"delivered": ["a@b.com"], "permanent_bounces": [], "queued": []}}


def _capture():
    """Patch urlopen and return (mock, list-that-fills-with-decoded-payloads)."""
    sent = []

    def fake_urlopen(request, timeout=None, context=None):
        sent.append({
            "url": request.full_url,
            "headers": dict(request.headers),
            "body": json.loads(request.data),
        })
        return FakeResponse(OK)

    return patch("urllib.request.urlopen", side_effect=fake_urlopen), sent


@override_settings(**CF_SETTINGS)
def test_posts_to_account_endpoint_with_bearer_token():
    patcher, sent = _capture()
    with patcher:
        assert send_mail("Subj", "Body", None, ["a@b.com"]) == 1
    assert sent[0]["url"] == (
        "https://api.cloudflare.com/client/v4/accounts/acct123/email/sending/send"
    )
    assert sent[0]["headers"]["Authorization"] == "Bearer token123"


@override_settings(**CF_SETTINGS)
def test_html_alternative_maps_to_html_field():
    patcher, sent = _capture()
    with patcher:
        msg = EmailMultiAlternatives("S", "plain text", None, ["a@b.com"])
        msg.attach_alternative("<h1>rich</h1>", "text/html")
        msg.send()
    body = sent[0]["body"]
    # Both parts matter: HTML-only hurts spam scoring.
    assert body["text"] == "plain text"
    assert body["html"] == "<h1>rich</h1>"


@override_settings(**CF_SETTINGS)
def test_reply_to_uses_snake_case():
    """REST wants reply_to; replyTo (the Workers spelling) is silently dropped."""
    patcher, sent = _capture()
    with patcher:
        EmailMultiAlternatives(
            "S", "b", None, ["a@b.com"], reply_to=["support@nocthink.com"]
        ).send()
    assert sent[0]["body"]["reply_to"] == "support@nocthink.com"
    assert "replyTo" not in sent[0]["body"]


@override_settings(**CF_SETTINGS)
def test_cc_and_bcc_forwarded():
    patcher, sent = _capture()
    with patcher:
        EmailMultiAlternatives(
            "S", "b", None, ["a@b.com"], cc=["c@d.com"], bcc=["e@f.com"]
        ).send()
    assert sent[0]["body"]["cc"] == ["c@d.com"]
    assert sent[0]["body"]["bcc"] == ["e@f.com"]


@override_settings(**CF_SETTINGS)
def test_attachment_is_base64_encoded():
    patcher, sent = _capture()
    with patcher:
        msg = EmailMultiAlternatives("S", "b", None, ["a@b.com"])
        msg.attach("report.pdf", b"%PDF-1.4 fake", "application/pdf")
        msg.send()
    att = sent[0]["body"]["attachments"][0]
    assert base64.b64decode(att["content"]) == b"%PDF-1.4 fake"
    assert att["filename"] == "report.pdf"
    assert att["type"] == "application/pdf"


@override_settings(**CF_SETTINGS)
def test_recipient_cap_enforced():
    from apps.core.email_backends import MAX_RECIPIENTS

    patcher, _ = _capture()
    with patcher, pytest.raises(ValueError, match="at most 50"):
        EmailMultiAlternatives(
            "S", "b", None, [f"u{i}@x.com" for i in range(MAX_RECIPIENTS + 1)]
        ).send()


@override_settings(**CF_SETTINGS)
def test_validation_error_is_not_retried():
    """A 400 is a malformed message; retrying wastes time and can duplicate."""
    import urllib.error

    calls = []

    def fail(request, timeout=None, context=None):
        calls.append(1)
        raise urllib.error.HTTPError(request.full_url, 400, "Bad Request", {}, None)

    with patch("urllib.request.urlopen", side_effect=fail):
        with pytest.raises(urllib.error.HTTPError):
            send_mail("S", "B", None, ["a@b.com"])
    assert len(calls) == 1


@override_settings(**CF_SETTINGS)
def test_server_error_is_retried_then_raises():
    import urllib.error

    calls = []

    def fail(request, timeout=None, context=None):
        calls.append(1)
        raise urllib.error.HTTPError(request.full_url, 500, "Server Error", {}, None)

    with patch("urllib.request.urlopen", side_effect=fail), \
            patch("time.sleep"):
        with pytest.raises(urllib.error.HTTPError):
            send_mail("S", "B", None, ["a@b.com"])
    # 1 initial + CLOUDFLARE_EMAIL_MAX_RETRIES
    assert len(calls) == 3


@override_settings(**CF_SETTINGS)
def test_fail_silently_swallows_errors():
    import urllib.error

    def fail(request, timeout=None, context=None):
        raise urllib.error.HTTPError(request.full_url, 400, "Bad", {}, None)

    with patch("urllib.request.urlopen", side_effect=fail):
        assert send_mail("S", "B", None, ["a@b.com"], fail_silently=True) == 0


@override_settings(**{**CF_SETTINGS, "CLOUDFLARE_EMAIL_API_TOKEN": ""})
def test_missing_credentials_raises():
    with pytest.raises(ValueError, match="CLOUDFLARE_ACCOUNT_ID"):
        send_mail("S", "B", None, ["a@b.com"])


@override_settings(**CF_SETTINGS)
def test_permanent_bounce_is_logged(caplog):
    """A bounce returns HTTP 200, so it must not read as a clean success."""
    bounced = {"success": True, "result": {"delivered": [], "permanent_bounces": ["x@y.com"], "queued": []}}

    with patch("urllib.request.urlopen", return_value=FakeResponse(bounced)):
        with caplog.at_level("WARNING"):
            send_mail("S", "B", None, ["x@y.com"])
    assert "cloudflare_email_bounced" in caplog.text


def test_uses_explicit_ca_bundle():
    """urllib's default CA store is empty on some hosts (macOS python.org
    builds, slim containers), which fails every send with
    CERTIFICATE_VERIFY_FAILED. certifi must be used when available."""
    from apps.core.email_backends import _ssl_context

    ctx = _ssl_context()
    assert len(ctx.get_ca_certs()) > 0, "no CA certificates loaded"


@override_settings(**CF_SETTINGS)
def test_ssl_context_is_passed_to_urlopen():
    import ssl

    seen = {}

    def fake(request, timeout=None, context=None):
        seen["context"] = context
        return FakeResponse(OK)

    with patch("urllib.request.urlopen", side_effect=fake):
        send_mail("S", "B", None, ["a@b.com"])
    assert isinstance(seen["context"], ssl.SSLContext)
    # Verification must never be disabled: the request carries an API token.
    assert seen["context"].verify_mode == ssl.CERT_REQUIRED
