"""A submission as a PDF document and the receipt email carrying it
(app/crud/form_documents.py). Mail is captured, not sent; the job queue is
run in-process (JOB_QUEUE=pytest)."""

import io
import uuid
from unittest.mock import patch

import pytest
from pypdf import PdfReader

from app.core import jobs, throttle
from app.crud.form_documents import fill
from app.models.reporting import Connector
from tests.conftest import AsyncSessionLocal


@pytest.fixture(autouse=True)
def _fresh_rate_limits():
    throttle.reset()
    yield


def _text(pdf: bytes) -> str:
    return "\n".join(page.extract_text() for page in PdfReader(io.BytesIO(pdf)).pages)


FIELDS = [
    {"name": "ref", "type": "autonumber", "label": "Application number", "autonumber_format": "APP-{SEQ:3}"},
    {"name": "name", "type": "text", "label": "Applicant", "required": True},
    {"name": "email", "type": "email", "label": "Email"},
    {"name": "home", "type": "address", "label": "Address"},
    {"name": "agree", "type": "consent", "label": "Declaration", "consent_text": "True to my knowledge."},
]
DOCUMENT = {
    "enabled": True,
    "title": "Acknowledgement — {name}",
    "body": "Dear {name},\n\nWe received your application to {form} on {submitted_at}.\n\n{answers}\n\nKeep this for your records. {nonsense}",
    "footer": "{workspace} — computer generated",
}


async def _form(client, headers, **settings) -> dict:
    created = await client.post("/forms", headers=headers, json={"name": f"Pytest Docs {uuid.uuid4().hex[:6]}", "field_definitions": FIELDS, "settings": settings})
    assert created.status_code == 201, created.text
    published = await client.post(f"/forms/{created.json()['form_id']}/publish", headers=headers)
    return published.json()


def test_fill_keeps_unknown_names():
    assert fill("{a} {b} {form} {zzz}", {"a": "1"}, {"form": "F"}, {"a", "b"}) == "1  F {zzz}"


async def test_submission_document(client, auth_headers, test_tenant):
    form = await _form(client, auth_headers, document=DOCUMENT)
    created = await client.post(
        "/form-submissions",
        headers=auth_headers,
        json={
            "form_id": form["form_id"],
            "form_version": form["version"],
            "field_values": {"name": "Asha Menon", "home": {"line1": "12 MG Road", "city": "Kochi"}, "agree": True},
        },
    )
    assert created.status_code == 201, created.text
    resp = await client.get(f"/form-submissions/{created.json()['id']}/document.pdf", headers=auth_headers)
    assert resp.status_code == 200, resp.text
    assert resp.headers["content-type"] == "application/pdf"
    assert 'filename="Pytest-Docs' in resp.headers["content-disposition"] and "APP-001.pdf" in resp.headers["content-disposition"]
    text = _text(resp.content)
    for expected in ("Acknowledgement — Asha Menon", "Dear Asha Menon,", "Reference APP-001", "12 MG Road", "Kochi", "Declaration", "Agreed", "{nonsense}", "computer generated"):
        assert expected in text, (expected, text)
    assert "Email" not in text  # blank answers aren't listed

    # A form without a document has nothing to download.
    plain = await _form(client, auth_headers)
    other = await client.post("/form-submissions", headers=auth_headers, json={"form_id": plain["form_id"], "form_version": plain["version"], "field_values": {"name": "B"}})
    assert (await client.get(f"/form-submissions/{other.json()['id']}/document.pdf", headers=auth_headers)).status_code == 404


async def test_receipt_email_with_the_pdf(client, auth_headers, test_tenant):
    tid = test_tenant["tenant_id"]
    async with AsyncSessionLocal() as db, db.begin():
        db.add(Connector(tenant_id=tid, name="Pytest mail", type="email_smtp", configuration={"host": "smtp.example.com", "from": "noreply@example.com"}, status="enabled"))
    receipt = {"enabled": True, "to_field": "email", "subject": "We got it, {name}", "message": "Thanks {name}.", "attach_pdf": True}
    form = await _form(client, auth_headers, document=DOCUMENT, receipt=receipt)

    created = await client.post(
        "/form-submissions",
        headers=auth_headers,
        json={"form_id": form["form_id"], "form_version": form["version"], "field_values": {"name": "Asha", "email": "asha@example.com"}},
    )
    assert created.status_code == 201, created.text
    sent: list[tuple] = []
    with patch("app.crud.form_documents._send_sync", side_effect=lambda cfg, to, subject, body, attachment: sent.append((to, subject, body, attachment))):
        await jobs.run_due(["forms.receipt"], tenant_id=tid)
    assert len(sent) == 1
    to, subject, body, attachment = sent[0]
    assert (to, subject) == ("asha@example.com", "We got it, Asha")
    assert body.startswith("Thanks Asha.") and "Reference: APP-001" in body
    assert attachment[0].endswith("APP-001.pdf") and "Dear Asha," in _text(attachment[1])

    # No address, no receipt.
    await client.post("/form-submissions", headers=auth_headers, json={"form_id": form["form_id"], "form_version": form["version"], "field_values": {"name": "No mail"}})
    with patch("app.crud.form_documents._send_sync", side_effect=lambda *a: sent.append(a)):
        await jobs.run_due(["forms.receipt"], tenant_id=tid)
    assert len(sent) == 1

    # The receipt has to go to an email field.
    bad = await client.post("/forms", headers=auth_headers, json={"name": "x", "field_definitions": FIELDS, "settings": {"receipt": {"enabled": True, "to_field": "name"}}})
    assert bad.status_code == 422 and "Receipt email" in bad.json()["detail"]


async def test_public_document_and_receipt_rules(client, auth_headers, test_tenant):
    tid = test_tenant["tenant_id"]
    # On a public form, the receipt only goes to a verified address.
    unverified = await client.post(
        "/forms",
        headers=auth_headers,
        json={"name": "x", "field_definitions": FIELDS, "settings": {"public_access": True, "receipt": {"enabled": True, "to_field": "email"}}},
    )
    assert unverified.status_code == 422
    assert "isn't verified" in unverified.json()["detail"]["message"]

    form = await _form(client, auth_headers, document=DOCUMENT, public_access=True)
    sent = await client.post(f"/public/forms/{tid}/{form['form_id']}/submissions", json={"field_values": {"name": "Ravi"}})
    assert sent.status_code == 201, sent.text
    token = sent.json()["document_token"]
    assert token
    pdf = await client.get(f"/public/forms/{tid}/{form['form_id']}/document.pdf", params={"token": token})
    assert pdf.status_code == 200 and "Dear Ravi," in _text(pdf.content)
    assert (await client.get(f"/public/forms/{tid}/{form['form_id']}/document.pdf", params={"token": "nope"})).status_code == 404
