from contextlib import contextmanager
from email.parser import BytesParser
from email.policy import default

import pytest

from ccma.services.welcome_mail import generate_and_send_welcome_mail, open_claims
from ccma.storage.repository import MemberRepository, RepositoryError


def _new_member_repository(tmp_path):
    repository = MemberRepository(tmp_path)
    repository.initialize()
    organization = repository.get_configuration()["organization"]
    organization.update(
        {
            "name": "Chaos Computer Club Mannheim e.V.",
            "email": "verwaltung@example.org",
            "iban": "DE98670505050038907751",
            "bic": "MANSDE66XXX",
        }
    )
    repository.save_organization(organization)
    member = repository.create_member(
        first_name="Ada", last_name="Lovelace", birth_date="1990-01-01"
    )
    member.email = "ada@example.org"
    member.accepted_at = "2026-08-20"
    repository.save_member(member)
    repository.create_manual_claim(
        member.member_id, title="Aufnahmegebühr", amount="15.00", due_date="2026-09-17"
    )
    repository.create_manual_claim(
        member.member_id,
        title="Mitgliedsbeitrag 2. Halbjahr 2026",
        amount="60.00",
        due_date="2026-09-17",
    )
    return repository, member


def test_welcome_mail_lists_open_claims_and_bank_details(tmp_path):
    repository, member = _new_member_repository(tmp_path)
    export_path = tmp_path / "Willkommen.eml"

    generated = generate_and_send_welcome_mail(
        repository,
        member.member_id,
        delivery_mode="local",
        output_path=export_path,
        sender_name="Verwaltung C3MA",
        sender_email="verwaltung@example.org",
        signature="Der Vorstand",
    )

    message = BytesParser(policy=default).parsebytes(generated.export_path.read_bytes())
    content = message.get_content()
    assert message["To"] == "ada@example.org"
    assert "Willkommen" in message["Subject"]
    assert "Aufnahmegebühr (fällig 17.09.2026): 15.00 Euro" in content
    assert "Mitgliedsbeitrag 2. Halbjahr 2026 (fällig 17.09.2026): 60.00 Euro" in content
    assert "Gesamtbetrag: 75.00 Euro" in content
    assert "DE98670505050038907751" in content
    assert content.rstrip().endswith("Der Vorstand")
    # No "Mitglied seit" is stored yet -- a member accepted in August starts on 1 September.
    assert "Ab dem 01.09.2026 bist du offiziell Mitglied" in content


def test_welcome_mail_is_archived_and_logged(tmp_path):
    repository, member = _new_member_repository(tmp_path)

    generated = generate_and_send_welcome_mail(
        repository,
        member.member_id,
        delivery_mode="local",
        output_path=tmp_path / "Willkommen.eml",
        sender_name="Verwaltung C3MA",
        sender_email="verwaltung@example.org",
        signature="Der Vorstand",
    )

    assert generated.archive_path.parent.name == "Willkommen"
    assert generated.archive_path.read_bytes() == generated.export_path.read_bytes()
    event = repository.get_events(member.member_id)[-1]
    assert event.event_type == "welcome_email_sent"
    assert event.data["recipient"] == "ada@example.org"
    assert len(event.data["claim_ids"]) == 2


def test_only_selected_claims_are_billed(tmp_path):
    repository, member = _new_member_repository(tmp_path)
    admission = next(
        claim for claim in open_claims(repository, member.member_id)
        if claim["title"] == "Aufnahmegebühr"
    )

    generated = generate_and_send_welcome_mail(
        repository,
        member.member_id,
        claim_ids=[admission["claim_id"]],
        delivery_mode="local",
        output_path=tmp_path / "Willkommen.eml",
        sender_name="Verwaltung C3MA",
        sender_email="verwaltung@example.org",
        signature="Der Vorstand",
    )

    content = BytesParser(policy=default).parsebytes(
        generated.export_path.read_bytes()
    ).get_content()
    assert "Aufnahmegebühr" in content
    assert "Mitgliedsbeitrag 2. Halbjahr 2026" not in content
    assert "Gesamtbetrag: 15.00 Euro" in content


def test_members_with_mandate_are_told_the_money_is_collected(tmp_path):
    repository, member = _new_member_repository(tmp_path)
    member.account_holder = "Ada Lovelace"
    member.iban = "DE98670505050038907751"
    member.mandate_reference = "MANDAT-42"
    member.mandate_signed_at = "2026-08-20"
    member.mandate_active = True
    repository.save_member(member)

    generated = generate_and_send_welcome_mail(
        repository,
        member.member_id,
        delivery_mode="local",
        output_path=tmp_path / "Willkommen.eml",
        sender_name="Verwaltung C3MA",
        sender_email="verwaltung@example.org",
        signature="Der Vorstand",
    )

    content = BytesParser(policy=default).parsebytes(
        generated.export_path.read_bytes()
    ).get_content()
    assert "SEPA-Lastschriftmandat erteilt (Mandatsreferenz MANDAT-42)" in content
    assert "Bitte überweise" not in content


def test_edited_template_is_used_for_the_mail(tmp_path):
    repository, member = _new_member_repository(tmp_path)
    repository.save_mail_template(
        "welcome",
        subject="Servus {{member.first_name}}",
        body="Du schuldest uns {{claims.total}} Euro.\n{{#claims}}* {{claim.title}}{{/claims}}",
    )

    generated = generate_and_send_welcome_mail(
        repository,
        member.member_id,
        delivery_mode="local",
        output_path=tmp_path / "Willkommen.eml",
        sender_name="Verwaltung C3MA",
        sender_email="verwaltung@example.org",
        signature="Der Vorstand",
    )

    message = BytesParser(policy=default).parsebytes(generated.export_path.read_bytes())
    assert message["Subject"] == "Servus Ada"
    assert message.get_content().replace("\r\n", "\n").strip() == (
        "Du schuldest uns 75.00 Euro.\n* Aufnahmegebühr\n* Mitgliedsbeitrag 2. Halbjahr 2026"
    )


def test_send_mode_delivers_via_smtp_without_local_file(tmp_path, monkeypatch):
    import ccma.services.welcome_mail as welcome_mail_module

    repository, member = _new_member_repository(tmp_path)
    repository.save_email_settings(
        delivery_mode="send",
        smtp_host="smtp.example.org",
        smtp_port=587,
        smtp_security="starttls",
        smtp_username="verwaltung",
        smtp_password="secret",
        imap_host="",
        imap_port=993,
        imap_security="ssl",
        imap_username="",
        imap_password="",
        imap_drafts_folder="INBOX.Entwürfe",
    )
    sent = []
    monkeypatch.setattr(
        welcome_mail_module, "smtp_session", contextmanager(lambda settings: iter(["client"]))
    )
    monkeypatch.setattr(
        welcome_mail_module, "send_via_smtp", lambda client, content: sent.append(content)
    )

    generated = generate_and_send_welcome_mail(
        repository,
        member.member_id,
        delivery_mode="send",
        sender_name="Verwaltung C3MA",
        sender_email="verwaltung@example.org",
        signature="Der Vorstand",
    )

    assert generated.export_path is None
    assert generated.archive_path.read_bytes() == sent[0]
    assert repository.get_events(member.member_id)[-1].data["delivery_mode"] == "send"


def test_member_without_email_is_refused(tmp_path):
    repository, member = _new_member_repository(tmp_path)
    member.email = ""
    repository.save_member(member)

    with pytest.raises(RepositoryError, match="E-Mail-Adresse"):
        generate_and_send_welcome_mail(
            repository,
            member.member_id,
            delivery_mode="local",
            output_path=tmp_path / "Willkommen.eml",
            sender_name="Verwaltung C3MA",
            sender_email="verwaltung@example.org",
            signature="Der Vorstand",
        )


def test_archiving_failure_after_smtp_success_still_logs_the_sent_mail(tmp_path, monkeypatch):
    import ccma.services.welcome_mail as welcome_mail_module

    repository, member = _new_member_repository(tmp_path)
    repository.save_email_settings(
        delivery_mode="send",
        smtp_host="mail.example.org",
        smtp_port=587,
        smtp_security="none",
        smtp_username="",
        smtp_password="",
        imap_host="",
        imap_port=993,
        imap_security="ssl",
        imap_username="",
        imap_password="",
        imap_drafts_folder="",
    )
    monkeypatch.setattr(
        welcome_mail_module, "smtp_session", contextmanager(lambda settings: iter(["client"]))
    )
    monkeypatch.setattr(welcome_mail_module, "send_via_smtp", lambda client, content: None)
    real_replace = welcome_mail_module.os.replace

    def _flaky_replace(src, dst):
        # Only the final move into the member file fails -- the mail is out by then.
        if str(dst).endswith(".eml") and "Willkommen" in str(dst):
            raise OSError("disk full")
        return real_replace(src, dst)

    monkeypatch.setattr(welcome_mail_module.os, "replace", _flaky_replace)

    with pytest.raises(RepositoryError, match="zugestellt"):
        generate_and_send_welcome_mail(
            repository,
            member.member_id,
            delivery_mode="send",
            sender_name="Verwaltung C3MA",
            sender_email="verwaltung@example.org",
            signature="Der Vorstand",
        )

    event = repository.get_events(member.member_id)[-1]
    assert event.event_type == "welcome_email_sent"
    assert event.data["archive_error"]
    assert "document" not in event.references


def test_a_template_broken_outside_ccma_stops_the_send(tmp_path):
    repository, member = _new_member_repository(tmp_path)
    # The templates are plain files in the store, so they can be edited (and broken)
    # with any text editor -- the send has to notice instead of mailing the marker.
    (repository.mail_templates_root / "willkommen.txt").write_text(
        "Betreff: Willkommen\n\nHallo,\n{{#claims}}\n{{claim.title}}\n",
        encoding="utf-8",
    )

    with pytest.raises(RepositoryError, match="wird nicht geschlossen"):
        generate_and_send_welcome_mail(
            repository,
            member.member_id,
            delivery_mode="local",
            output_path=tmp_path / "Willkommen.eml",
            sender_name="Verwaltung C3MA",
            sender_email="verwaltung@example.org",
            signature="Der Vorstand",
        )

    assert not (tmp_path / "Willkommen.eml").exists()
    archive = repository.members_root / member.member_id / "files" / "documents" / "Willkommen"
    assert not archive.exists()
    assert repository.get_events(member.member_id)[-1].event_type != "welcome_email_sent"
