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

import pytest
from test_reminders import _overdue_claim_repository

from ccma.services.reminder_mail import generate_and_send_reminder_mail
from ccma.storage.repository import RepositoryError


def test_reminder_mail_is_archived_and_marks_reminder_sent(tmp_path):
    repository, member = _overdue_claim_repository(tmp_path / "store")
    member.email = "reminder@example.org"
    repository.save_member(member)
    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)
    reminder = repository.create_reminder_draft(
        member.member_id,
        "claim-1",
        level=1,
        name="Zahlungserinnerung",
        payment_deadline_days=14,
        detail="Bitte Mitgliedsnummer angeben.",
    )
    export_path = tmp_path / "Zahlungserinnerung.eml"

    generated = generate_and_send_reminder_mail(
        repository,
        member.member_id,
        "claim-1",
        reminder["reminder_id"],
        delivery_mode="local",
        output_path=export_path,
        sender_name="Verwaltung C3MA",
        sender_email="verwaltung@example.org",
        signature="Der Vorstand",
    )

    assert generated.export_path.read_bytes() == generated.archive_path.read_bytes()
    message = BytesParser(policy=default).parsebytes(generated.export_path.read_bytes())
    assert message["To"] == "reminder@example.org"
    assert "Zahlungserinnerung" in message["Subject"]
    assert "100.00 Euro" in message.get_content()
    assert "DE98670505050038907751" in message.get_content()
    data = repository.get_contributions(member.member_id)
    assert data.reminders[0]["status"] == "sent"
    assert data.reminders[0]["payment_deadline"]
    assert repository.get_events(member.member_id)[-1].event_type == "reminder_email_sent"
    assert generated.archive_path.parent.name == "Mahnungen"


def test_reminder_mail_includes_fee_in_open_balance(tmp_path):
    repository, member = _overdue_claim_repository(tmp_path / "store")
    member.email = "reminder@example.org"
    repository.save_member(member)
    reminder = repository.create_reminder_draft(
        member.member_id,
        "claim-1",
        level=1,
        name="Erste Mahnung",
        payment_deadline_days=14,
        items=[{"description": "Mahngebühr", "amount": "5.00"}],
    )

    generated = generate_and_send_reminder_mail(
        repository,
        member.member_id,
        "claim-1",
        reminder["reminder_id"],
        delivery_mode="local",
        output_path=tmp_path / "Mahnung.eml",
        sender_name="Verwaltung C3MA",
        sender_email="verwaltung@example.org",
        signature="Der Vorstand",
    )

    message = BytesParser(policy=default).parsebytes(generated.export_path.read_bytes())
    assert "Offener Betrag: 105.00 Euro" in message.get_content()
    assert "Mahngebühr: 5.00 Euro" in message.get_content()


def test_reminder_mail_send_mode_delivers_via_smtp_without_local_file(tmp_path, monkeypatch):
    import ccma.services.reminder_mail as reminder_mail_module

    repository, member = _overdue_claim_repository(tmp_path / "store")
    member.email = "reminder@example.org"
    repository.save_member(member)
    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="",
    )
    reminder = repository.create_reminder_draft(
        member.member_id,
        "claim-1",
        level=1,
        name="Zahlungserinnerung",
        payment_deadline_days=14,
    )

    sent = []
    fake_smtp_session = contextmanager(lambda settings: iter([object()]))
    monkeypatch.setattr(reminder_mail_module, "smtp_session", fake_smtp_session)
    monkeypatch.setattr(
        reminder_mail_module, "send_via_smtp", lambda client, content: sent.append(content)
    )

    generated = generate_and_send_reminder_mail(
        repository,
        member.member_id,
        "claim-1",
        reminder["reminder_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.is_file()
    assert len(sent) == 1
    data = repository.get_contributions(member.member_id)
    assert data.reminders[0]["status"] == "sent"


def test_reminder_mail_send_mode_also_copies_to_sent_folder_when_enabled(tmp_path, monkeypatch):
    import ccma.services.reminder_mail as reminder_mail_module

    repository, member = _overdue_claim_repository(tmp_path / "store")
    member.email = "reminder@example.org"
    repository.save_member(member)
    repository.save_email_settings(
        delivery_mode="send",
        smtp_host="mail.example.org",
        smtp_port=587,
        smtp_security="none",
        smtp_username="",
        smtp_password="",
        imap_host="mail.example.org",
        imap_port=993,
        imap_security="ssl",
        imap_username="",
        imap_password="",
        imap_drafts_folder="",
        imap_sent_enabled=True,
        imap_sent_folder="INBOX.Gesendet",
    )
    reminder = repository.create_reminder_draft(
        member.member_id,
        "claim-1",
        level=1,
        name="Zahlungserinnerung",
        payment_deadline_days=14,
    )

    monkeypatch.setattr(
        reminder_mail_module, "smtp_session", contextmanager(lambda settings: iter(["smtp-client"]))
    )
    monkeypatch.setattr(reminder_mail_module, "send_via_smtp", lambda client, content: None)
    monkeypatch.setattr(
        reminder_mail_module, "imap_session", contextmanager(lambda settings: iter(["imap-client"]))
    )
    ensured = []
    appended = []
    monkeypatch.setattr(
        reminder_mail_module, "ensure_imap_folder", lambda client, folder: ensured.append((client, folder))
    )
    monkeypatch.setattr(
        reminder_mail_module,
        "append_message",
        lambda client, content, *, folder, flags: appended.append((client, folder, flags)),
    )

    generate_and_send_reminder_mail(
        repository,
        member.member_id,
        "claim-1",
        reminder["reminder_id"],
        delivery_mode="send",
        sender_name="Verwaltung C3MA",
        sender_email="verwaltung@example.org",
        signature="Der Vorstand",
    )

    assert ensured == [("imap-client", "INBOX.Gesendet")]
    assert appended == [("imap-client", "INBOX.Gesendet", r"(\Seen)")]


def test_reminder_mail_send_failure_reverts_sent_status(tmp_path, monkeypatch):
    import ccma.services.reminder_mail as reminder_mail_module

    repository, member = _overdue_claim_repository(tmp_path / "store")
    member.email = "reminder@example.org"
    repository.save_member(member)
    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="",
    )
    reminder = repository.create_reminder_draft(
        member.member_id,
        "claim-1",
        level=1,
        name="Zahlungserinnerung",
        payment_deadline_days=14,
        items=[{"description": "Mahngebühr", "amount": "5.00"}],
    )

    @contextmanager
    def _fake_smtp_session(settings):
        yield "smtp-client"

    monkeypatch.setattr(reminder_mail_module, "smtp_session", _fake_smtp_session)

    def _failing_send(client, content):
        raise RepositoryError("E-Mail konnte nicht versandt werden: connection reset")

    monkeypatch.setattr(reminder_mail_module, "send_via_smtp", _failing_send)

    with pytest.raises(RepositoryError):
        generate_and_send_reminder_mail(
            repository,
            member.member_id,
            "claim-1",
            reminder["reminder_id"],
            delivery_mode="send",
            sender_name="Verwaltung C3MA",
            sender_email="verwaltung@example.org",
            signature="Der Vorstand",
        )

    data = repository.get_contributions(member.member_id)
    stored_reminder = data.reminders[0]
    assert stored_reminder["status"] == "draft"
    assert not stored_reminder.get("fee_item_ids")
    assert repository.get_events(member.member_id)[-1].event_type == "reminder_sent_reverted"
    _data, claim = repository.get_claim(member.member_id, "claim-1")
    assert not any(
        item.get("description") == "Mahngebühr" for item in claim.get("items", [])
    )


def test_reminder_mail_local_write_failure_reverts_sent_status(tmp_path, monkeypatch):
    repository, member = _overdue_claim_repository(tmp_path / "store")
    member.email = "reminder@example.org"
    repository.save_member(member)
    reminder = repository.create_reminder_draft(
        member.member_id,
        "claim-1",
        level=1,
        name="Zahlungserinnerung",
        payment_deadline_days=14,
    )

    import ccma.services.reminder_mail as reminder_mail_module

    def _boom(*args, **kwargs):
        raise OSError("disk full")

    monkeypatch.setattr(reminder_mail_module.os, "replace", _boom)

    with pytest.raises(OSError):
        generate_and_send_reminder_mail(
            repository,
            member.member_id,
            "claim-1",
            reminder["reminder_id"],
            delivery_mode="local",
            output_path=tmp_path / "Zahlungserinnerung.eml",
            sender_name="Verwaltung C3MA",
            sender_email="verwaltung@example.org",
            signature="Der Vorstand",
        )

    data = repository.get_contributions(member.member_id)
    assert data.reminders[0]["status"] == "draft"


def test_reminder_mail_archiving_failure_after_smtp_success_does_not_revert(tmp_path, monkeypatch):
    import ccma.services.reminder_mail as reminder_mail_module

    repository, member = _overdue_claim_repository(tmp_path / "store")
    member.email = "reminder@example.org"
    repository.save_member(member)
    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="",
    )
    reminder = repository.create_reminder_draft(
        member.member_id,
        "claim-1",
        level=1,
        name="Zahlungserinnerung",
        payment_deadline_days=14,
        items=[{"description": "Mahngebühr", "amount": "5.00"}],
    )

    @contextmanager
    def _fake_smtp_session(settings):
        yield "smtp-client"

    monkeypatch.setattr(reminder_mail_module, "smtp_session", _fake_smtp_session)
    monkeypatch.setattr(reminder_mail_module, "send_via_smtp", lambda client, content: None)

    real_replace = reminder_mail_module.os.replace

    def _flaky_replace(src, dst):
        # Only the final archive move (into the "Mahnungen" folder) should fail --
        # the mail has already been handed to send_via_smtp by that point.
        if str(dst).endswith(".eml") and "Mahnungen" in str(dst):
            raise OSError("disk full")
        return real_replace(src, dst)

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

    with pytest.raises(RepositoryError, match="zugestellt"):
        generate_and_send_reminder_mail(
            repository,
            member.member_id,
            "claim-1",
            reminder["reminder_id"],
            delivery_mode="send",
            sender_name="Verwaltung C3MA",
            sender_email="verwaltung@example.org",
            signature="Der Vorstand",
        )

    data = repository.get_contributions(member.member_id)
    stored_reminder = data.reminders[0]
    assert stored_reminder["status"] == "sent", "mail was already sent -- must not be un-sent"
    assert stored_reminder.get("fee_item_ids")
    events = repository.get_events(member.member_id)
    assert events[-1].event_type == "reminder_email_sent"
    assert events[-1].data["archive_error"]
    assert "document" not in events[-1].references


def test_reminder_mail_archive_dir_creation_failure_reverts_sent_status(tmp_path, monkeypatch):
    from pathlib import Path

    repository, member = _overdue_claim_repository(tmp_path / "store")
    member.email = "reminder@example.org"
    repository.save_member(member)
    reminder = repository.create_reminder_draft(
        member.member_id,
        "claim-1",
        level=1,
        name="Zahlungserinnerung",
        payment_deadline_days=14,
        items=[{"description": "Mahngebühr", "amount": "5.00"}],
    )

    original_mkdir = Path.mkdir

    def _flaky_mkdir(self, *args, **kwargs):
        if self.name == "Mahnungen":
            raise OSError("permission denied")
        return original_mkdir(self, *args, **kwargs)

    monkeypatch.setattr(Path, "mkdir", _flaky_mkdir)

    with pytest.raises(OSError):
        generate_and_send_reminder_mail(
            repository,
            member.member_id,
            "claim-1",
            reminder["reminder_id"],
            delivery_mode="local",
            output_path=tmp_path / "Zahlungserinnerung.eml",
            sender_name="Verwaltung C3MA",
            sender_email="verwaltung@example.org",
            signature="Der Vorstand",
        )

    data = repository.get_contributions(member.member_id)
    stored_reminder = data.reminders[0]
    assert stored_reminder["status"] == "draft"
    assert not stored_reminder.get("fee_item_ids")
