import pytest

from ccma.domain.mail_templates import (
    MAIL_TEMPLATES,
    MailTemplate,
    MailTemplateBlock,
    MailTemplateError,
    MailTemplateSpec,
    default_mail_template,
    parse_mail_template,
    placeholder_entries,
    render_mail_template,
    serialize_mail_template,
    validate_block_markers,
    validate_mail_template,
)
from ccma.storage.repository import MemberRepository, RepositoryError


def test_subject_header_round_trips_through_file_format() -> None:
    text = serialize_mail_template("Hallo {{member.first_name}}", "Zeile eins\nZeile zwei")

    template = parse_mail_template(text)

    assert text.startswith("Betreff: Hallo {{member.first_name}}\n\n")
    assert template == MailTemplate("Hallo {{member.first_name}}", "Zeile eins\nZeile zwei")


def test_shipped_defaults_only_use_declared_placeholders() -> None:
    for spec in MAIL_TEMPLATES:
        template = default_mail_template(spec.key)
        validate_mail_template(spec.key, template.subject, template.body)


def test_rendering_fills_values_and_expands_repeat_blocks() -> None:
    template = MailTemplate(
        "Beitrag für {{member.first_name}}",
        "Hallo {{member.first_name}},\n"
        "{{#claims}}- {{claim.title}}: {{claim.amount}}{{/claims}}\n"
        "Summe: {{claims.total}}",
    )

    rendered = render_mail_template(
        template,
        {"member.first_name": "Ada", "claims.total": "75.00"},
        {
            "claims": [
                {"claim.title": "Aufnahmegebühr", "claim.amount": "15.00"},
                {"claim.title": "Mitgliedsbeitrag", "claim.amount": "60.00"},
            ]
        },
    )

    assert rendered.subject == "Beitrag für Ada"
    assert rendered.body == (
        "Hallo Ada,\n- Aufnahmegebühr: 15.00\n- Mitgliedsbeitrag: 60.00\nSumme: 75.00"
    )


def test_line_holding_only_an_empty_placeholder_is_dropped() -> None:
    template = MailTemplate("Betreff", "Erste Zeile\n{{reminder.hint}}\nLetzte Zeile")

    assert render_mail_template(template, {"reminder.hint": ""}).body == "Erste Zeile\nLetzte Zeile"
    assert (
        render_mail_template(template, {"reminder.hint": "Hinweis: X"}).body
        == "Erste Zeile\nHinweis: X\nLetzte Zeile"
    )


def test_unknown_placeholder_is_rejected_while_editing() -> None:
    with pytest.raises(MailTemplateError, match="member.favourite_colour"):
        validate_mail_template("welcome", "Betreff", "Hallo {{member.favourite_colour}}")


def test_store_copies_are_seeded_editable_and_resettable(tmp_path) -> None:
    repository = MemberRepository(tmp_path)
    repository.initialize()
    path = repository.mail_templates_root / "willkommen.txt"
    assert path.is_file()

    repository.save_mail_template("welcome", subject="Moin {{member.first_name}}", body="Kurz und knapp.")
    assert repository.get_mail_template("welcome") == MailTemplate(
        "Moin {{member.first_name}}", "Kurz und knapp."
    )
    assert "Moin {{member.first_name}}" in path.read_text(encoding="utf-8")

    repository.reset_mail_template("welcome")
    assert repository.get_mail_template("welcome") == default_mail_template("welcome")


def test_missing_template_file_falls_back_to_shipped_default(tmp_path) -> None:
    repository = MemberRepository(tmp_path)
    repository.initialize()
    (repository.mail_templates_root / "mahnung.txt").unlink()

    template = repository.get_mail_template("reminder")

    assert template == default_mail_template("reminder")
    assert (repository.mail_templates_root / "mahnung.txt").is_file()


def test_saving_a_broken_template_is_refused(tmp_path) -> None:
    repository = MemberRepository(tmp_path)
    repository.initialize()

    with pytest.raises(RepositoryError, match="Platzhalter"):
        repository.save_mail_template("reminder", subject="Mahnung", body="Hallo {{member.nonsense}}")
    with pytest.raises(RepositoryError, match="Betreff"):
        repository.save_mail_template("reminder", subject="   ", body="Hallo")


def test_block_rows_insert_a_working_block_not_its_description() -> None:
    entries = placeholder_entries("welcome")
    block = next(entry for entry in entries if entry.label.startswith("{{#claims}}"))

    # The label may abbreviate the block, the inserted snippet may not.
    assert block.label == "{{#claims}} … {{/claims}}"
    assert block.snippet.startswith("{{#claims}}\n")
    assert block.snippet.rstrip().endswith("{{/claims}}")
    assert "{{claim.description}}" in block.snippet
    assert "…" not in block.snippet
    assert all("…" not in entry.snippet for entry in entries)


def test_inserted_block_validates_and_renders_one_line_per_claim() -> None:
    block = next(
        entry for entry in placeholder_entries("welcome") if entry.label.startswith("{{#claims}}")
    )
    body = f"Hallo {{{{member.first_name}}}},\n\n{block.snippet}\nSumme: {{{{claims.total}}}}"

    validate_mail_template("welcome", "Betreff", body)
    rendered = render_mail_template(
        MailTemplate("Betreff", body),
        {"member.first_name": "Ada", "claims.total": "75.00"},
        {
            "claims": [
                {
                    "claim.description": "Aufnahmegebühr",
                    "claim.due_date": "17.09.2026",
                    "claim.balance": "15.00",
                },
                {
                    "claim.description": "Mitgliedsbeitrag 2. Halbjahr 2026",
                    "claim.due_date": "17.09.2026",
                    "claim.balance": "60.00",
                },
            ]
        },
    )

    assert "Aufnahmegebühr (fällig 17.09.2026): 15.00 Euro" in rendered.body
    assert "Mitgliedsbeitrag 2. Halbjahr 2026 (fällig 17.09.2026): 60.00 Euro" in rendered.body
    assert "…" not in rendered.body


def test_every_block_snippet_is_valid_in_its_own_template() -> None:
    for spec in MAIL_TEMPLATES:
        for block in spec.blocks:
            validate_mail_template(spec.key, "Betreff", block.snippet)


@pytest.mark.parametrize(
    ("body", "expected"),
    [
        ("Hallo\n{{#claims}}\n{{claim.title}}", "wird nicht geschlossen"),
        ("Hallo\n{{/claims}}\nGruß", "ohne zugehöriges"),
        ("{{#claims}}\n{{claim.title}}\n{{/claims}}\n{{/claims}}", "ohne zugehöriges"),
        ("{{#claims}}\n{{#claims}}\nx\n{{/claims}}\n{{/claims}}", "verschachtelt"),
        ("{{#members}}\nx\n{{/members}}", "Unbekannter Wiederholungsblock"),
        ("{{ # claims }}\nx", "wird nicht geschlossen"),
    ],
)
def test_unbalanced_repeat_markers_are_rejected(body, expected) -> None:
    with pytest.raises(MailTemplateError, match=expected):
        validate_mail_template("welcome", "Betreff", body)


def test_repeat_markers_in_the_subject_are_rejected() -> None:
    with pytest.raises(MailTemplateError, match="Betreff"):
        validate_mail_template("welcome", "Beitrag {{#claims}}", "Hallo")


def test_a_template_without_blocks_rejects_every_marker() -> None:
    with pytest.raises(MailTemplateError, match="Verfügbar: keine"):
        validate_mail_template("sepa", "Betreff", "{{#claims}}\nx\n{{/claims}}")


def test_a_closing_marker_must_match_the_open_one(monkeypatch) -> None:
    two_blocks = MailTemplateSpec(
        key="two-blocks",
        label="Test",
        filename="test.txt",
        description="",
        placeholders=(),
        blocks=(
            MailTemplateBlock("first", "", (("item.a", ""),), "{{item.a}}"),
            MailTemplateBlock("second", "", (("item.b", ""),), "{{item.b}}"),
        ),
    )
    monkeypatch.setattr("ccma.domain.mail_templates.MAIL_TEMPLATES", (two_blocks,))

    with pytest.raises(MailTemplateError, match="Erwartet wird \\{\\{/first\\}\\}"):
        validate_block_markers("two-blocks", "Betreff", "{{#first}}\nx\n{{/second}}")


def test_a_broken_template_is_not_stored(tmp_path) -> None:
    repository = MemberRepository(tmp_path)
    repository.initialize()
    path = repository.mail_templates_root / "willkommen.txt"
    before = path.read_text(encoding="utf-8")

    with pytest.raises(RepositoryError, match="wird nicht geschlossen"):
        repository.save_mail_template(
            "welcome", subject="Willkommen", body="Hallo\n{{#claims}}\n{{claim.title}}"
        )

    assert path.read_text(encoding="utf-8") == before
