"""
Portal-Views: Öffentlich zugängliche Seiten für Destinatäre (kein Login erforderlich).

Workflow Upload-Portal:
  1. Destinatär erhält E-Mail mit Einmallink (Token)
  2. GET /portal/upload/<token>/  → Formular anzeigen
  3. POST /portal/upload/<token>/ → Dateien hochladen, Token einlösen
  4. Redirect → /portal/upload/<token>/danke/

Workflow Onboarding-Portal (neue Destinatäre):
  1. Verwaltung sendet OnboardingEinladung per E-Mail
  2. GET/POST /portal/onboarding/<token>/schritt/<n>/  → je Schritt ein Formular
  3. Schritte 1-5 via Session-State (kein Login)
  4. Nach Schritt 5: Destinatär (unbestaetigt) anlegen + Stiftung benachrichtigen

Sicherheit:
  - Token ist 64 Zeichen, kryptographisch sicher
  - Einmalige Nutzung (abgeschlossen_am wird gesetzt)
  - Automatische Ablaufzeit (30 Tage)
  - CSRF-Schutz aktiv
"""
import hashlib
import logging
import mimetypes
import os

from django.contrib import messages
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from django.views.decorators.cache import never_cache
from django.views.decorators.http import require_http_methods

from stiftung.models import DokumentDatei, OnboardingEinladung, UploadToken, VierteljahresNachweis


def datenschutzerklaerung(request):
    """Datenschutzerklärung für das öffentliche Portal."""
    return render(request, "portal/datenschutzerklaerung.html")

logger = logging.getLogger(__name__)

# Erlaubte Dateitypen für Uploads
ERLAUBTE_MIME_TYPES = {
    "application/pdf",
    "image/jpeg",
    "image/png",
    "image/tiff",
}
MAX_DATEIGROESSE = 20 * 1024 * 1024  # 20 MB


def _get_client_ip(request):
    """Extrahiert die Client-IP-Adresse aus dem Request."""
    x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
    if x_forwarded_for:
        return x_forwarded_for.split(",")[0].strip()
    return request.META.get("REMOTE_ADDR", "")


@never_cache
@require_http_methods(["GET", "POST"])
def upload_formular(request, token):
    """
    Zeigt das Upload-Formular für einen Nachweis-Token an
    und verarbeitet den Datei-Upload.
    """
    upload_token = get_object_or_404(
        UploadToken.objects.select_related("destinataer", "nachweis"),
        token=token,
    )

    # Token-Gültigkeitsprüfung
    if not upload_token.ist_gueltig():
        if upload_token.eingeloest_am is not None:
            return render(
                request,
                "portal/upload_fehler.html",
                {
                    "fehler_typ": "bereits_verwendet",
                    "message": "Dieser Upload-Link wurde bereits verwendet.",
                },
                status=410,
            )
        return render(
            request,
            "portal/upload_fehler.html",
            {
                "fehler_typ": "abgelaufen",
                "message": "Dieser Upload-Link ist abgelaufen. "
                           "Bitte wenden Sie sich an die Stiftung.",
            },
            status=410,
        )

    destinataer = upload_token.destinataer
    nachweis = upload_token.nachweis
    halbjahr = 1 if nachweis.quartal in [1, 2] else 2
    halbjahr_label = f"{halbjahr}. Halbjahr {nachweis.jahr}"

    base_context = {
        "token": upload_token,
        "destinataer": destinataer,
        "nachweis": nachweis,
        "halbjahr_label": halbjahr_label,
        "gueltig_bis": upload_token.gueltig_bis,
        "max_dateigroesse_mb": MAX_DATEIGROESSE // (1024 * 1024),
    }

    if request.method == "GET":
        return render(request, "portal/upload_formular.html", base_context)

    # POST: Einwilligung prüfen
    einwilligung = request.POST.get("einwilligung")
    if not einwilligung:
        ctx = {
            **base_context,
            "einwilligung_fehler": "Bitte erteilen Sie Ihre Einwilligung zur Datenverarbeitung, um fortzufahren.",
        }
        for kat in [
            "studiennachweis", "einkommenssituation", "vermogenssituation", "weitere_dokumente"
        ]:
            ctx[f"{kat}_text"] = request.POST.get(f"{kat}_text", "")
        return render(request, "portal/upload_formular.html", ctx)

    # POST: Kategorisierte Dateien und Texte verarbeiten
    # Kategorien mit ihren DMS-Kontext-Werten und FK-Feldern auf VierteljahresNachweis
    KATEGORIEN = [
        {
            "key": "studiennachweis",
            "label": "Studiennachweis",
            "kontext": "studiennachweis",
            "dms_fk_field": "studiennachweis_dms_dokument",
            "text_field": "studiennachweis_bemerkung",
            "bestaetigt_field": "studiennachweis_eingereicht",
            "pflicht": True,
        },
        {
            "key": "einkommenssituation",
            "label": "Einkommenssituation",
            "kontext": "einkommenssituation",
            "dms_fk_field": "einkommenssituation_dms_dokument",
            "text_field": "einkommenssituation_text",
            "bestaetigt_field": "einkommenssituation_bestaetigt",
            "pflicht": True,
        },
        {
            "key": "vermogenssituation",
            "label": "Vermögenssituation",
            "kontext": "vermoegenssituation",
            "dms_fk_field": "vermogenssituation_dms_dokument",
            "text_field": "vermogenssituation_text",
            "bestaetigt_field": "vermogenssituation_bestaetigt",
            "pflicht": True,
        },
        {
            "key": "weitere_dokumente",
            "label": "Weitere Dokumente",
            "kontext": "sonstiges",
            "dms_fk_field": None,
            "text_field": "weitere_dokumente_beschreibung",
            "bestaetigt_field": None,
            "pflicht": False,
        },
    ]

    fehler_liste = []
    gespeicherte_dokumente = []
    nachweis_update_fields = []

    for kat in KATEGORIEN:
        datei = request.FILES.get(kat["key"])
        text = request.POST.get(f"{kat['key']}_text", "").strip()

        # Pflichtprüfung: mindestens Datei oder Text
        if kat["pflicht"] and not datei and not text:
            fehler_liste.append(
                f'Bitte laden Sie für „{kat["label"]}" eine Datei hoch '
                f"oder geben Sie einen Texteintrag ein."
            )
            continue

        # Datei verarbeiten
        if datei:
            if datei.size > MAX_DATEIGROESSE:
                fehler_liste.append(
                    f'„{kat["label"]}": Datei „{datei.name}" ist zu groß (max. 20 MB).'
                )
            else:
                mime_type, _ = mimetypes.guess_type(datei.name)
                if mime_type not in ERLAUBTE_MIME_TYPES:
                    fehler_liste.append(
                        f'„{kat["label"]}": Dateiformat von „{datei.name}" '
                        f"nicht erlaubt (PDF, JPG, PNG, TIFF)."
                    )
                else:
                    try:
                        dok = DokumentDatei(
                            titel=f"{kat['label']} {halbjahr_label}: {os.path.splitext(datei.name)[0]}",
                            beschreibung=f"Hochgeladen über Upload-Portal am {timezone.now().strftime('%d.%m.%Y')}",
                            kontext=kat["kontext"],
                            datei=datei,
                            dateiname_original=datei.name,
                            dateityp=mime_type or "application/octet-stream",
                            dateigroesse=datei.size,
                            destinataer=destinataer,
                        )
                        dok.save()
                        nachweis.nachweis_dokumente.add(dok)
                        gespeicherte_dokumente.append(dok)

                        # Kategorie-spezifische FK setzen
                        if kat["dms_fk_field"]:
                            setattr(nachweis, kat["dms_fk_field"], dok)
                            nachweis_update_fields.append(kat["dms_fk_field"])

                        # Bestätigt-Flag setzen
                        if kat["bestaetigt_field"]:
                            setattr(nachweis, kat["bestaetigt_field"], True)
                            nachweis_update_fields.append(kat["bestaetigt_field"])
                    except Exception as exc:
                        logger.exception("Fehler beim Speichern von %s (%s): %s", datei.name, kat["label"], exc)
                        fehler_liste.append(
                            f'Fehler beim Speichern von „{datei.name}" ({kat["label"]}).'
                        )

        # Text verarbeiten
        if text:
            if kat["text_field"]:
                setattr(nachweis, kat["text_field"], text)
                nachweis_update_fields.append(kat["text_field"])
            # Auch bei reinem Text: bestätigt setzen
            if not datei and kat["bestaetigt_field"]:
                setattr(nachweis, kat["bestaetigt_field"], True)
                nachweis_update_fields.append(kat["bestaetigt_field"])

    # Bei Pflicht-Fehlern und keinen gespeicherten Dokumenten: Formular erneut anzeigen
    if fehler_liste and not gespeicherte_dokumente:
        ctx = {**base_context, "fehler": " ".join(fehler_liste)}
        # Texte wieder einfüllen
        for kat in KATEGORIEN:
            ctx[f"{kat['key']}_text"] = request.POST.get(f"{kat['key']}_text", "")
        return render(request, "portal/upload_formular.html", ctx)

    # Nachweis-Felder speichern
    if nachweis_update_fields:
        nachweis.save(update_fields=list(set(nachweis_update_fields)))

    # DSGVO-Einwilligung protokollieren (Art. 7 Abs. 1 DSGVO)
    upload_token.einwilligung_erteilt_am = timezone.now()
    upload_token.save(update_fields=["einwilligung_erteilt_am"])

    # Token einlösen
    ip = _get_client_ip(request)
    upload_token.einloesen(ip_address=ip)

    # Nachweis-Status aktualisieren
    if nachweis.status in ("offen", "nachbesserung"):
        nachweis.status = "eingereicht"
        nachweis.eingereicht_am = timezone.now()
        nachweis.save(update_fields=["status", "eingereicht_am"])

    logger.info(
        "Upload-Portal: %d Datei(en) für Destinatär %s (Nachweis %s) gespeichert.",
        len(gespeicherte_dokumente),
        destinataer.id,
        nachweis.id,
    )

    return redirect("portal:upload_danke", token=token)


@never_cache
def upload_danke(request, token):
    """Bestätigungsseite nach erfolgreichem Upload."""
    upload_token = get_object_or_404(
        UploadToken.objects.select_related("destinataer", "nachweis"),
        token=token,
    )
    nachweis = upload_token.nachweis
    halbjahr = 1 if nachweis.quartal in [1, 2] else 2
    halbjahr_label = f"{halbjahr}. Halbjahr {nachweis.jahr}"

    return render(
        request,
        "portal/upload_danke.html",
        {
            "destinataer": upload_token.destinataer,
            "nachweis": nachweis,
            "halbjahr_label": halbjahr_label,
        },
    )


# =============================================================================
# Onboarding-Portal: Mehrstufiges Antragsformular für neue Destinatäre
# =============================================================================

ONBOARDING_SCHRITTE = 5
SESSION_KEY = "onboarding_data"

ERLAUBTE_MIME_TYPES_ONBOARDING = {
    "application/pdf",
    "image/jpeg",
    "image/png",
    "image/tiff",
}
MAX_DATEIGROESSE_ONBOARDING = 20 * 1024 * 1024  # 20 MB


def _get_onboarding_einladung(token):
    """Holt und validiert eine OnboardingEinladung anhand des Tokens."""
    try:
        einladung = OnboardingEinladung.objects.get(token=token)
    except OnboardingEinladung.DoesNotExist:
        return None, "nicht_gefunden"
    if not einladung.ist_gueltig():
        if einladung.status == "abgeschlossen":
            return None, "bereits_abgeschlossen"
        return None, "abgelaufen"
    return einladung, None


def _onboarding_fehler(request, fehler_typ):
    """Rendert die Fehlerseite für das Onboarding-Portal."""
    return render(
        request,
        "portal/onboarding_fehler.html",
        {"fehler_typ": fehler_typ},
        status=410,
    )


@never_cache
def onboarding_schritt(request, token, schritt=1):
    """
    Mehrstufiges Onboarding-Formular für neue Destinatäre.
    Schritt 1-5, sessionbasiert, kein Login erforderlich.
    """
    einladung, fehler = _get_onboarding_einladung(token)
    if fehler:
        return _onboarding_fehler(request, fehler)

    schritt = int(schritt)
    if schritt < 1 or schritt > ONBOARDING_SCHRITTE:
        return redirect("portal:onboarding_schritt", token=token, schritt=1)

    session_key = f"{SESSION_KEY}_{token}"
    data = request.session.get(session_key, {})

    # Navigationspfade: Zurück-Button
    if request.method == "POST" and request.POST.get("aktion") == "zurueck" and schritt > 1:
        return redirect("portal:onboarding_schritt", token=token, schritt=schritt - 1)

    if request.method == "POST":
        if schritt == 1:
            return _onboarding_schritt1_post(request, token, einladung, data, session_key)
        elif schritt == 2:
            return _onboarding_schritt2_post(request, token, einladung, data, session_key)
        elif schritt == 3:
            return _onboarding_schritt3_post(request, token, einladung, data, session_key)
        elif schritt == 4:
            return _onboarding_schritt4_post(request, token, einladung, data, session_key)
        elif schritt == 5:
            return _onboarding_schritt5_post(request, token, einladung, data, session_key)

    # GET: Formular anzeigen
    context = {
        "einladung": einladung,
        "token": token,
        "schritt": schritt,
        "schritte_gesamt": ONBOARDING_SCHRITTE,
        "data": data,
    }
    return render(request, f"portal/onboarding_schritt{schritt}.html", context)


def _onboarding_schritt1_post(request, token, einladung, data, session_key):
    """Schritt 1: Datenschutzerklärung + Erklärung des Leistungsempfängers."""
    if not request.POST.get("dse_zustimmung"):
        context = {
            "einladung": einladung,
            "token": token,
            "schritt": 1,
            "schritte_gesamt": ONBOARDING_SCHRITTE,
            "data": data,
            "fehler": "Bitte stimmen Sie der Datenschutzerklärung zu, um fortzufahren.",
        }
        return render(request, "portal/onboarding_schritt1.html", context)
    if not request.POST.get("merkblatt_zustimmung"):
        context = {
            "einladung": einladung,
            "token": token,
            "schritt": 1,
            "schritte_gesamt": ONBOARDING_SCHRITTE,
            "data": data,
            "fehler": "Bitte bestätigen Sie die Erklärung des Leistungsempfängers.",
        }
        return render(request, "portal/onboarding_schritt1.html", context)

    data["schritt1"] = {
        "dse_zustimmung": True,
        "dse_zeitstempel": timezone.now().isoformat(),
        "merkblatt_zustimmung": True,
    }
    request.session[session_key] = data
    return redirect("portal:onboarding_schritt", token=token, schritt=2)


def _onboarding_schritt2_post(request, token, einladung, data, session_key):
    """Schritt 2: Persönliche Daten (Merkblatt 1-4)."""
    pflichtfelder = ["vorname", "nachname", "geburtsdatum", "strasse", "plz", "ort",
                     "email", "telefon", "verwandtschaftsverhaeltnis"]
    fehlende = [f for f in pflichtfelder if not request.POST.get(f, "").strip()]

    if fehlende:
        context = {
            "einladung": einladung,
            "token": token,
            "schritt": 2,
            "schritte_gesamt": ONBOARDING_SCHRITTE,
            "data": data,
            "post_data": request.POST,
            "fehler": "Bitte füllen Sie alle Pflichtfelder aus.",
            "fehlende_felder": fehlende,
        }
        return render(request, "portal/onboarding_schritt2.html", context)

    data["schritt2"] = {
        "vorname": request.POST["vorname"].strip(),
        "nachname": request.POST["nachname"].strip(),
        "geburtsdatum": request.POST["geburtsdatum"].strip(),
        "strasse": request.POST["strasse"].strip(),
        "plz": request.POST["plz"].strip(),
        "ort": request.POST["ort"].strip(),
        "email": request.POST["email"].strip(),
        "telefon": request.POST["telefon"].strip(),
        "handynummer": request.POST.get("handynummer", "").strip(),
        "verwandtschaftsverhaeltnis": request.POST["verwandtschaftsverhaeltnis"].strip(),
        "familienzweig": request.POST.get("familienzweig", "").strip(),
    }
    request.session[session_key] = data
    return redirect("portal:onboarding_schritt", token=token, schritt=3)


def _onboarding_schritt3_post(request, token, einladung, data, session_key):
    """Schritt 3: Ausbildung/Studium (Merkblatt 5-6)."""
    in_ausbildung = request.POST.get("in_ausbildung") == "ja"

    data["schritt3"] = {
        "in_ausbildung": in_ausbildung,
        "ausbildungsart": request.POST.get("ausbildungsart", "").strip(),
        "institution": request.POST.get("institution", "").strip(),
        "voraussichtliche_dauer": request.POST.get("voraussichtliche_dauer", "").strip(),
    }
    request.session[session_key] = data
    return redirect("portal:onboarding_schritt", token=token, schritt=4)


def _onboarding_schritt4_post(request, token, einladung, data, session_key):
    """Schritt 4: Finanzielle Situation (Merkblatt 7-12)."""
    data["schritt4"] = {
        "haushaltstyp": request.POST.get("haushaltstyp", "").strip(),
        "haushaltsgroesse": request.POST.get("haushaltsgroesse", "").strip(),
        "monatliche_bezuege": request.POST.get("monatliche_bezuege", "").strip(),
        "bezuege_art": request.POST.get("bezuege_art", "").strip(),
        "unterhalt": request.POST.get("unterhalt", "").strip(),
        "miete_heizung": request.POST.get("miete_heizung", "").strip(),
        "vermoegen": request.POST.get("vermoegen", "").strip(),
        "lebensunterhalt_aufwendungen": request.POST.get("lebensunterhalt_aufwendungen", "").strip(),
    }
    request.session[session_key] = data
    return redirect("portal:onboarding_schritt", token=token, schritt=5)


def _onboarding_schritt5_post(request, token, einladung, data, session_key):
    """Schritt 5: Zusammenfassung, Datei-Upload und Bestätigung."""
    if not request.POST.get("finale_bestaetigung"):
        context = {
            "einladung": einladung,
            "token": token,
            "schritt": 5,
            "schritte_gesamt": ONBOARDING_SCHRITTE,
            "data": data,
            "fehler": "Bitte bestätigen Sie die Richtigkeit Ihrer Angaben.",
        }
        return render(request, "portal/onboarding_schritt5.html", context)

    # Dateien prüfen und im DMS speichern (werden dem neuen Destinatär zugeordnet)
    from stiftung.models import Destinataer, DokumentDatei

    schritt2 = data.get("schritt2", {})
    schritt3 = data.get("schritt3", {})

    # Neuen Destinatär anlegen (unbestätigt – 4-Augen-Prinzip)
    try:
        import datetime
        geb_str = schritt2.get("geburtsdatum", "")
        geburtsdatum = None
        if geb_str:
            try:
                geburtsdatum = datetime.date.fromisoformat(geb_str)
            except ValueError:
                pass

        destinataer = Destinataer(
            vorname=schritt2.get("vorname", ""),
            nachname=schritt2.get("nachname", ""),
            geburtsdatum=geburtsdatum,
            email=schritt2.get("email", ""),
            telefon=schritt2.get("telefon", ""),
            strasse=schritt2.get("strasse", ""),
            plz=schritt2.get("plz", ""),
            ort=schritt2.get("ort", ""),
            familienzweig=schritt2.get("familienzweig") or "anderer",
            unterstuetzung_bestaetigt=False,
            aktiv=False,  # Erst nach Vorstandsfreigabe aktivieren
        )
        destinataer.save()
    except Exception as exc:
        logger.exception("Fehler beim Anlegen des Destinatärs aus Onboarding: %s", exc)
        context = {
            "einladung": einladung,
            "token": token,
            "schritt": 5,
            "schritte_gesamt": ONBOARDING_SCHRITTE,
            "data": data,
            "fehler": "Technischer Fehler beim Speichern. Bitte versuchen Sie es erneut.",
        }
        return render(request, "portal/onboarding_schritt5.html", context)

    # Hochgeladene Dokumente im DMS speichern
    dms_dokumente_gespeichert = []
    for datei_key, datei in request.FILES.items():
        if datei.size > MAX_DATEIGROESSE_ONBOARDING:
            continue
        mime_type, _ = mimetypes.guess_type(datei.name)
        if mime_type not in ERLAUBTE_MIME_TYPES_ONBOARDING:
            continue
        try:
            dok = DokumentDatei(
                titel=f"Onboarding-Dokument: {os.path.splitext(datei.name)[0]}",
                beschreibung=f"Onboarding von {destinataer.vorname} {destinataer.nachname}",
                kontext="onboarding",
                datei=datei,
                dateiname_original=datei.name,
                dateityp=mime_type or "application/octet-stream",
                dateigroesse=datei.size,
                destinataer=destinataer,
            )
            dok.save()
            dms_dokumente_gespeichert.append(dok)
        except Exception as exc:
            logger.exception("Fehler beim Speichern von Onboarding-Dokument %s: %s", datei.name, exc)

    # Einladung als abgeschlossen markieren
    einladung.abgeschlossen_am = timezone.now()
    einladung.status = "abgeschlossen"
    einladung.destinataer = destinataer
    einladung.save(update_fields=["abgeschlossen_am", "status", "destinataer"])

    # Interne Benachrichtigung: E-Mail an Stiftung
    _benachrichtige_stiftung_onboarding(destinataer, einladung, data)

    # Session aufräumen
    if session_key in request.session:
        del request.session[session_key]

    logger.info(
        "Onboarding abgeschlossen: Destinatär %s angelegt (Einladung %s), %d Dokumente.",
        destinataer.id,
        einladung.id,
        len(dms_dokumente_gespeichert),
    )

    return redirect("portal:onboarding_danke", token=token)


def _benachrichtige_stiftung_onboarding(destinataer, einladung, data):
    """Sendet eine interne Benachrichtigungs-E-Mail nach Abschluss des Onboardings."""
    from django.conf import settings
    from django.core.mail import EmailMessage

    from stiftung.utils.config import get_config

    empfaenger = get_config("notification_email") or getattr(settings, "STIFTUNG_NOTIFICATION_EMAIL", settings.DEFAULT_FROM_EMAIL)
    subject = f"Neues Onboarding abgeschlossen: {destinataer.vorname} {destinataer.nachname}"
    body = (
        f"Ein neues Onboarding-Verfahren wurde abgeschlossen.\n\n"
        f"Name: {destinataer.vorname} {destinataer.nachname}\n"
        f"E-Mail: {destinataer.email}\n"
        f"Einladung: {einladung.id}\n\n"
        f"Bitte prüfen und freigeben:\n"
        f"{getattr(settings, 'SITE_URL', 'https://vhtv-stiftung.de')}"
        f"/destinataere/{destinataer.id}/\n\n"
        f"Der Destinatär ist noch NICHT aktiv (unterstuetzung_bestaetigt=False).\n"
        f"Freigabe durch den Vorstand erforderlich.\n"
    )
    try:
        from_email = get_config("smtp_from_email") or settings.DEFAULT_FROM_EMAIL
        EmailMessage(subject, body, from_email, [empfaenger]).send()
    except Exception as exc:
        logger.warning("Onboarding-Benachrichtigung konnte nicht gesendet werden: %s", exc)


@never_cache
def onboarding_danke(request, token):
    """Abschlussseite nach erfolgreichem Onboarding."""
    try:
        einladung = OnboardingEinladung.objects.select_related("destinataer").get(
            token=token, status="abgeschlossen"
        )
    except OnboardingEinladung.DoesNotExist:
        return render(
            request,
            "portal/onboarding_fehler.html",
            {"fehler_typ": "nicht_gefunden"},
            status=404,
        )
    return render(
        request,
        "portal/onboarding_danke.html",
        {"einladung": einladung},
    )
