"""Unit tests: MIME construction, HTML validation, HTML->PDF, attachment filenames."""
import base64

import pytest

from app.services.message_builder import (
    build_mime_message,
    generate_attachment_filename,
    html_to_pdf,
    sanitize_html_for_email,
    sanitize_preview,
    validate_html,
)


class TestMime:
    def test_alternative_structure_html_and_text(self):
        msg = build_mime_message(
            from_email="sender@gmail.com", to_email="to@x.com", subject="Invoice",
            html_body="<p>Hi</p>", text_body="Hi")
        assert msg.get_content_type() == "multipart/alternative"
        assert msg["From"] == "sender@gmail.com"
        assert msg["To"] == "to@x.com"
        assert msg["Subject"] == "Invoice"
        assert msg["Message-ID"]
        assert msg["Date"]
        parts = list(msg.walk())
        types = [p.get_content_type() for p in parts]
        assert "text/plain" in types and "text/html" in types

    def test_mixed_when_only_html(self):
        msg = build_mime_message("s@gmail.com", "t@x.com", "S", html_body="<p>x</p>")
        assert msg.get_content_type() == "multipart/mixed"
        types = [p.get_content_type() for p in msg.walk()]
        assert "text/html" in types

    def test_from_header_is_authenticated_sender(self):
        # From MUST equal the authenticated account — never spoofed
        msg = build_mime_message("real@gmail.com", "to@x.com", "S", html_body="<p>x</p>")
        assert msg["From"] == "real@gmail.com"

    def test_attachment_pdf(self):
        pdf = html_to_pdf("<h1>Invoice 123</h1><p>Total $100.00</p>")
        assert pdf[:4] == b"%PDF"
        msg = build_mime_message("s@gmail.com", "t@x.com", "S", html_body="<p>x</p>",
                                 attachments=[{"filename": "Invoice_INV-1.pdf",
                                               "content": pdf, "mime_type": "application/pdf"}])
        parts = [p for p in msg.walk() if p.get_content_type() == "application/pdf"]
        assert len(parts) == 1
        assert parts[0].get_filename() == "Invoice_INV-1.pdf"
        payload = parts[0].get_payload(decode=True)
        assert payload[:4] == b"%PDF"

    def test_attachment_image(self):
        png = base64.b64decode(
            "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==")
        msg = build_mime_message("s@gmail.com", "t@x.com", "S", text_body="x",
                                 attachments=[{"filename": "sig.png", "content": png,
                                               "mime_type": "image/png"}])
        assert any(p.get_content_type() == "image/png" for p in msg.walk())

    def test_list_unsubscribe_headers(self):
        msg = build_mime_message("s@gmail.com", "t@x.com", "S", text_body="x",
                                 unsubscribe_url="https://app.test/u/abc",
                                 unsubscribe_mailto="unsub@company.com")
        assert msg["List-Unsubscribe"] == "<https://app.test/u/abc>, <mailto:unsub@company.com>"
        assert msg["List-Unsubscribe-Post"] == "List-Unsubscribe=One-Click"

    def test_no_unsub_headers_when_empty(self):
        msg = build_mime_message("s@gmail.com", "t@x.com", "S", text_body="x")
        assert msg["List-Unsubscribe"] is None

    def test_roundtrip_bytes(self):
        msg = build_mime_message("s@gmail.com", "t@x.com", "Invoice 99",
                                 html_body="<p>Amount $1,240.00</p>", text_body="Amount $1,240.00")
        raw = msg.as_bytes()
        assert b"Invoice 99" in raw
        # bodies are base64-encoded in transit; decoded payloads match exactly
        for p in msg.walk():
            if p.get_content_type() in ("text/plain", "text/html"):
                assert b"$1,240.00" in p.get_payload(decode=True)


class TestHtmlValidation:
    def test_clean_html_passes(self):
        assert validate_html("<p>Hello <b>%Name%</b></p>".replace("%Name%", "Alex")) == []

    def test_disallowed_tags(self):
        for bad in ["<script>alert(1)</script>", "<iframe src='x'></iframe>",
                    "<object></object>", "<embed>", "<form></form>"]:
            problems = validate_html(f"<p>ok</p>{bad}")
            assert problems, f"no problem reported for {bad}"

    def test_unsubstituted_variables_detected(self):
        problems = validate_html("<p>Hi %FirstName%</p>")
        assert any("Unsubstituted" in p for p in problems)

    def test_empty_html(self):
        assert "Empty HTML body" in validate_html("")

    def test_img_without_src(self):
        assert any("img without src" in p for p in validate_html("<p><img alt='x'></p>"))


class TestPdf:
    def test_real_pdf_bytes(self):
        pdf = html_to_pdf("<html><body><h1>Invoice</h1><p>Amount: $1,240.00</p></body></html>")
        assert pdf[:4] == b"%PDF"
        assert b"%%EOF" in pdf  # complete PDF trailer
        assert len(pdf) > 500

    def test_unicode_renders(self):
        pdf = html_to_pdf("<p>Café — résumé €250</p>")
        assert pdf[:4] == b"%PDF"

    def test_empty_html_yields_valid_empty_pdf(self):
        # WeasyPrint renders an empty document to a valid (blank) PDF
        pdf = html_to_pdf("")
        assert pdf[:4] == b"%PDF"


class TestSanitization:
    def test_script_neutralized(self):
        # bleach escapes disallowed tags so they cannot execute
        out = sanitize_preview('<p>ok</p><script>alert(1)</script>')
        assert "<script" not in out  # no raw opening tag
        assert out.count("&lt;script&gt;") >= 1  # escaped to inert text

    def test_script_content_cannot_reexecute(self):
        out = sanitize_preview('<p>ok</p><script>alert(1)</script>')
        # the escaped text is inert markup, not an executable node
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(out, "lxml")
        assert soup.find("script") is None

    def test_style_and_links_kept(self):
        out = sanitize_preview('<p style="color:red">x</p><a href="https://a.com">L</a>')
        assert 'href="https://a.com"' in out

    def test_email_variant_matches_preview(self):
        assert sanitize_html_for_email("<p>x</p>") == sanitize_preview("<p>x</p>")

    def test_table_markup_kept_for_invoice_layouts(self):
        out = sanitize_preview("<table><tr><td>$100</td></tr></table>")
        assert "<table>" in out and "<td>" in out


class TestAttachmentFilenames:
    def test_unique_per_invoice(self):
        a = generate_attachment_filename("invoice", "INV-ABC123")
        b = generate_attachment_filename("invoice", "INV-DEF456")
        assert a != b and a == "Invoice_INV-ABC123.pdf" and b == "Invoice_INV-DEF456.pdf"

    @pytest.mark.parametrize("kind,prefix", [
        ("invoice", "Invoice"), ("order", "Order"), ("receipt", "Receipt"),
    ])
    def test_kinds(self, kind, prefix):
        fn = generate_attachment_filename(kind, "INV-1")
        assert fn.startswith(prefix + "_") and fn.endswith(".pdf")

    def test_sanitized_invoice_number(self):
        fn = generate_attachment_filename("invoice", "INV-1/../../etc/passwd")
        assert "/" not in fn and ".." not in fn

    def test_unknown_kind_still_document(self):
        fn = generate_attachment_filename("other", "INV-9")
        assert fn.startswith("Document_") and fn.endswith(".pdf")

    def test_empty_invoice_number_falls_back_random(self):
        fns = {generate_attachment_filename("invoice", "") for _ in range(5)}
        assert all(f.endswith(".pdf") for f in fns) and len(fns) == 5
