from __future__ import annotations

import hashlib
import os
import re
from dataclasses import dataclass
from datetime import datetime
from email.message import EmailMessage
from email.policy import SMTP
from email.utils import format_datetime, make_msgid
from pathlib import Path

from ccma.domain.contributions import claim_balance
from ccma.domain.dates import format_date_for_display
from ccma.storage.repository import MemberRepository, RepositoryError


@dataclass(frozen=True, slots=True)
class GeneratedReminderMail:
    export_path: Path
    archive_path: Path
    sha256: str


def _safe_filename(value: str) -> str:
    cleaned = re.sub(r"[^A-Za-z0-9._-]+", "-", value.strip()).strip("-._")
    return cleaned or "Mahnung"


def _available_path(directory: Path, filename: str) -> Path:
    candidate = directory / filename
    if not candidate.exists():
        return candidate
    stem, suffix = Path(filename).stem, Path(filename).suffix
    index = 2
    while (directory / f"{stem}-{index}{suffix}").exists():
        index += 1
    return directory / f"{stem}-{index}{suffix}"


def reminder_mail_bytes(
    *,
    recipient: str,
    first_name: str,
    reminder: dict,
    claim: dict,
    balance: str,
    organization: dict,
    sender_name: str,
    sender_email: str,
    signature: str,
    created_at: datetime | None = None,
) -> bytes:
    if not recipient.strip():
        raise RepositoryError("Für das Mitglied ist keine E-Mail-Adresse hinterlegt.")
    if not sender_email.strip() or "@" not in sender_email:
        raise RepositoryError("Für die Mahnung ist eine gültige Absenderadresse erforderlich.")
    timestamp = created_at or datetime.now().astimezone()
    message = EmailMessage(policy=SMTP)
    message["Message-ID"] = make_msgid(domain=sender_email.rsplit("@", 1)[-1])
    message["Date"] = format_datetime(timestamp)
    message["From"] = f"{sender_name.strip()} <{sender_email.strip()}>"
    message["To"] = recipient.strip()
    message["Subject"] = f"{reminder.get('name', 'Zahlungserinnerung')} – {claim.get('title', '')}"
    message["X-Mozilla-Draft-Info"] = (
        "internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0; attachmentreminder=0"
    )
    iban = str(organization.get("iban", "")).strip()
    bic = str(organization.get("bic", "")).strip()
    payment_details = f"IBAN: {iban}" + (f"\nBIC: {bic}" if bic else "")
    detail = str(reminder.get("detail", "")).strip()
    message.set_content(
        "\n".join(
            (
                f"Hallo {first_name.strip() or 'Mitglied'},",
                "",
                f"zu der Forderung „{claim.get('title', 'Offene Forderung')}“ "
                "ist noch ein Betrag offen.",
                "",
                f"Offener Betrag: {balance} Euro",
                f"Ursprünglich fällig: "
                f"{format_date_for_display(str(claim.get('due_date', '')))}",
                f"Neue Zahlungsfrist: "
                f"{format_date_for_display(str(reminder.get('payment_deadline', '')))}",
                f"Mahngebühr: {reminder.get('fee', '0.00')} Euro",
                f"Hinweis: {detail}" if detail else "",
                "",
                "Bitte überweise den offenen Betrag unter Angabe deiner Mitgliedsnummer an:",
                payment_details,
                "",
                "Sollte sich deine Zahlung mit dieser Nachricht überschnitten haben, "
                "betrachte sie bitte als gegenstandslos.",
                "",
                "Viele Grüße",
                signature.strip(),
            )
        ),
        charset="utf-8",
    )
    return message.as_bytes()


def generate_and_send_reminder_mail(
    repository: MemberRepository,
    member_id: str,
    claim_id: str,
    reminder_id: str,
    *,
    output_path: Path | str,
    sender_name: str,
    sender_email: str,
    signature: str,
) -> GeneratedReminderMail:
    member = repository.get_member(member_id)
    data, claim = repository.get_claim(member_id, claim_id)
    reminder = repository._find_reminder(data, claim_id, reminder_id)
    if str(reminder.get("status", "")) not in {"draft", "generated"}:
        raise RepositoryError("Nur ein Mahnungsentwurf kann als E-Mail erzeugt werden.")
    if not member.email.strip():
        raise RepositoryError("Für das Mitglied ist keine E-Mail-Adresse hinterlegt.")
    if not sender_email.strip() or "@" not in sender_email:
        raise RepositoryError("Für die Mahnung ist eine gültige Absenderadresse erforderlich.")
    target = Path(output_path)
    target.parent.mkdir(parents=True, exist_ok=True)
    if target.exists():
        raise RepositoryError(f"Die Ausgabedatei existiert bereits: {target}")

    sent_reminder = repository.mark_reminder_sent(member_id, claim_id, reminder_id)
    updated_data, updated_claim = repository.get_claim(member_id, claim_id)
    organization = repository.get_configuration().get("organization") or {}
    content = reminder_mail_bytes(
        recipient=member.email,
        first_name=member.first_name,
        reminder=sent_reminder,
        claim=updated_claim,
        balance=f"{claim_balance(updated_data, updated_claim):.2f}",
        organization=organization,
        sender_name=sender_name,
        sender_email=sender_email,
        signature=signature,
    )
    archive_dir = repository.members_root / member_id / "files" / "documents" / "Mahnungen"
    archive_dir.mkdir(parents=True, exist_ok=True)
    filename = (
        f"{datetime.now().date().isoformat()}-"
        f"{_safe_filename(str(sent_reminder.get('name', 'Mahnung')))}.eml"
    )
    archive_path = _available_path(archive_dir, filename)
    export_temp = target.with_name(f".{target.name}.tmp")
    archive_temp = archive_path.with_name(f".{archive_path.name}.tmp")
    try:
        export_temp.write_bytes(content)
        archive_temp.write_bytes(content)
        os.replace(export_temp, target)
        os.replace(archive_temp, archive_path)
    finally:
        export_temp.unlink(missing_ok=True)
        archive_temp.unlink(missing_ok=True)
    relative_path = archive_path.relative_to(
        repository.members_root / member_id / "files"
    ).as_posix()
    digest = hashlib.sha256(content).hexdigest()
    repository.append_event(
        member_id,
        event_type="reminder_email_sent",
        summary=f"Mahnung per E-Mail versandt: {sent_reminder.get('name', '')}",
        actor_type="user",
        actor_name="Vorstand",
        references={
            "claim_id": claim_id,
            "reminder_id": reminder_id,
            "document": relative_path,
        },
        data={
            "recipient": member.email,
            "sha256": digest,
            "payment_deadline": str(sent_reminder.get("payment_deadline", "")),
        },
    )
    return GeneratedReminderMail(target, archive_path, digest)
