"""P-06: Zentrale Rechte-Policy fuer Medien-Uploads und Promotionen.

Konservative Erstannahmen (p06-v1-conservative):
- Alle Uploads (inkl. private) erfordern vollstaendige Erklaerung
- Personenfragen bei allen Sichtbarkeiten Pflicht zu beantworten
- Promotion zu hoeherem Niveau erfordert neue Erklaerung
- Altmedien ('legacy_unreviewed') duerfen nicht promoted werden

VORLAEUTIG: Juristische Validierung der Felder und Texte steht aus.
"""
from __future__ import annotations

import json as _json
from typing import Any, Optional

from fastapi import HTTPException

DECLARATION_VERSION = "p06-v1-conservative"

# Sichtbarkeits-Hierarchie: private(1) < club(2) < official(3)
VISIBILITY_LEVELS: dict[str, int] = {
    "private": 1,
    "club": 2,
    "official": 3,
}


def visibility_level(vis: str) -> int:
    return VISIBILITY_LEVELS.get((vis or "").strip().lower(), 0)


def rights_covers_target(declared_for: Optional[str], target_vis: str) -> bool:
    """True wenn die vorhandene Erklaerung die Ziel-Sichtbarkeit abdeckt."""
    if not declared_for:
        return False
    return visibility_level(declared_for) >= visibility_level(target_vis)


# --------------------------------------------------------------------------
# Validierung einer eingehenden Erklaerung
# --------------------------------------------------------------------------

def validate_rights_declaration(decl: dict[str, Any], target_visibility: str) -> None:
    """Pruefen ob alle Pflichtfelder der konservativen Erstannahme vorliegen.

    Wirft HTTPException 400 mit maschinenlesbarem code bei Verstoss.
    Gilt fuer alle Sichtbarkeiten (private/club/official) identisch.
    """
    # 1. rights_holder_confirmed ist immer Pflicht
    if not decl.get("rights_holder_confirmed"):
        raise HTTPException(
            status_code=400,
            detail={
                "code": "RIGHTS_DECLARATION_REQUIRED",
                "message": (
                    "Bitte bestaetigen, dass du die erforderlichen Rechte an diesem Medium besitzt."
                ),
            },
        )

    # 2. contains_identifiable_persons muss explizit beantwortet sein
    if decl.get("contains_identifiable_persons") is None:
        raise HTTPException(
            status_code=400,
            detail={
                "code": "RIGHTS_DECLARATION_REQUIRED",
                "message": "Bitte angeben, ob erkennbare Personen abgebildet sind.",
            },
        )

    # 3. Wenn Personen vorhanden: Einwilligung Pflicht
    if decl.get("contains_identifiable_persons") is True:
        if not decl.get("person_consent_confirmed"):
            raise HTTPException(
                status_code=400,
                detail={
                    "code": "PERSON_CONSENT_REQUIRED",
                    "message": (
                        "Bitte bestaetigen, dass die Einwilligungen aller erkennbaren Personen vorliegen."
                    ),
                },
            )

    # 4. contains_minors muss explizit beantwortet sein
    if decl.get("contains_minors") is None:
        raise HTTPException(
            status_code=400,
            detail={
                "code": "RIGHTS_DECLARATION_REQUIRED",
                "message": "Bitte angeben, ob Minderjaehrige abgebildet sind.",
            },
        )

    # 5. Wenn Minderjaehrige: Elterneinwilligung Pflicht
    if decl.get("contains_minors") is True:
        if not decl.get("parental_consent_confirmed"):
            raise HTTPException(
                status_code=400,
                detail={
                    "code": "PARENTAL_CONSENT_REQUIRED",
                    "message": (
                        "Bitte bestaetigen, dass die Einwilligungen der Sorgeberechtigten vorliegen."
                    ),
                },
            )

    # 6. contains_music muss explizit beantwortet sein
    if decl.get("contains_music") is None:
        raise HTTPException(
            status_code=400,
            detail={
                "code": "RIGHTS_DECLARATION_REQUIRED",
                "message": "Bitte angeben, ob das Medium Musik enthaelt.",
            },
        )

    # 7. Wenn Musik: Musikrechte Pflicht
    if decl.get("contains_music") is True:
        if not decl.get("music_rights_confirmed"):
            raise HTTPException(
                status_code=400,
                detail={
                    "code": "MUSIC_RIGHTS_REQUIRED",
                    "message": (
                        "Bitte bestaetigen, dass die erforderlichen Musikrechte vorliegen."
                    ),
                },
            )

    # 8. contains_third_party_content muss explizit beantwortet sein
    if decl.get("contains_third_party_content") is None:
        raise HTTPException(
            status_code=400,
            detail={
                "code": "RIGHTS_DECLARATION_REQUIRED",
                "message": "Bitte angeben, ob fremde geschuetzte Inhalte (Logos, Grafiken etc.) enthalten sind.",
            },
        )

    # 9. Wenn Fremdmaterial: Rechte Pflicht
    if decl.get("contains_third_party_content") is True:
        if not decl.get("third_party_rights_confirmed"):
            raise HTTPException(
                status_code=400,
                detail={
                    "code": "THIRD_PARTY_RIGHTS_REQUIRED",
                    "message": (
                        "Bitte bestaetigen, dass die Rechte an allen enthaltenen Fremdmaterialien vorliegen."
                    ),
                },
            )


# --------------------------------------------------------------------------
# Pruefen ob vorhandene Erklaerung Zielsichtbarkeit abdeckt
# --------------------------------------------------------------------------

def check_rights_coverage(cur: Any, asset_id: int, target_visibility: str) -> str:
    """Status der Rechteabdeckung fuer ein Asset und eine Zielsichtbarkeit.

    Returns:
        'ok'               - vorhandene Erklaerung reicht aus
        'legacy'           - Altmedium ohne Erklaerung (legacy_unreviewed)
        'blocked'          - durch Admin gesperrt
        'no_declaration'   - neues Medium ohne Erklaerung (sollte nicht vorkommen)

    Hinweis: Eine P-06-Erklaerung beschreibt den Inhalt (Rechteinhaber, Personen, Musik etc.)
    und ist sichtbarkeitsunabhaengig. rights_status='declared' gilt daher fuer alle
    Sichtbarkeits-Stufen ohne Levelvergleich.
    """
    cur.execute(
        "SELECT rights_status FROM media_assets WHERE id = %s",
        (asset_id,),
    )
    row = cur.fetchone()
    if not row:
        return "no_declaration"

    rs = (row[0] if not hasattr(row, "keys") else row["rights_status"] or "").strip().lower()

    if rs == "blocked":
        return "blocked"
    if rs == "legacy_unreviewed":
        return "legacy"
    if rs == "declared":
        return "ok"
    return "no_declaration"


def assert_rights_for_promotion(cur: Any, asset_id: int, target_visibility: str) -> None:
    """Wirft HTTPException wenn das Asset keine gueltige Erklaerung fuer target_visibility hat."""
    status = check_rights_coverage(cur, asset_id, target_visibility)
    if status == "ok":
        return
    if status == "legacy":
        raise HTTPException(
            status_code=400,
            detail={
                "code": "LEGACY_REDECLARATION_REQUIRED",
                "message": (
                    "Dieses Medium wurde vor Einfuehrung der Einwilligungspflicht hochgeladen. "
                    "Bitte eine Rechterklaerung nachreichen, bevor die Sichtbarkeit erhoeht wird."
                ),
                "asset_id": asset_id,
            },
        )
    if status == "blocked":
        raise HTTPException(
            status_code=403,
            detail={
                "code": "RIGHTS_BLOCKED",
                "message": "Dieses Medium ist durch einen Administrator gesperrt.",
                "asset_id": asset_id,
            },
        )
    # no_declaration (neues Medium ohne Erklaerung)
    raise HTTPException(
        status_code=400,
        detail={
            "code": "RIGHTS_DECLARATION_REQUIRED",
            "message": "Fuer dieses Medium liegt keine Rechterklaerung vor.",
            "asset_id": asset_id,
        },
    )


def assert_rights_for_exercise_link(cur: Any, asset_id: int, exercise_visibility: str) -> None:
    """Pruefen ob das Asset in eine Uebung mit dieser Sichtbarkeit eingebunden werden darf."""
    status = check_rights_coverage(cur, asset_id, exercise_visibility)
    if status == "ok":
        return
    if status == "legacy" and exercise_visibility == "private":
        # Altmedien duerfen in private Uebungen eingebunden bleiben (kein Upgrade-Risiko)
        return
    if status == "legacy":
        raise HTTPException(
            status_code=400,
            detail={
                "code": "LEGACY_REDECLARATION_REQUIRED",
                "message": (
                    "Das gewahlte Archiv-Medium hat noch keine Rechterklaerung nach neuem Standard. "
                    "Bitte zuerst eine Erklaerung fuer dieses Medium abgeben."
                ),
                "asset_id": asset_id,
            },
        )
    if status == "insufficient":
        raise HTTPException(
            status_code=400,
            detail={
                "code": "RIGHTS_SCOPE_INSUFFICIENT",
                "message": (
                    f"Das Archiv-Medium hat keine Erklaerung fuer Sichtbarkeit '{exercise_visibility}'. "
                    "Bitte zuerst eine neue Erklaerung fuer dieses Medium abgeben."
                ),
                "asset_id": asset_id,
            },
        )
    if status == "blocked":
        raise HTTPException(
            status_code=403,
            detail={
                "code": "RIGHTS_BLOCKED",
                "message": "Dieses Medium ist gesperrt und kann nicht verwendet werden.",
                "asset_id": asset_id,
            },
        )


# --------------------------------------------------------------------------
# Declaration-Log schreiben + Schnellfelder aktualisieren
# --------------------------------------------------------------------------

def _clean_context(val: Any) -> Optional[str]:
    """Leere Strings → None, sonst auf 2000 Zeichen kuerzen."""
    s = (val or "").strip()
    return s[:2000] if s else None


def write_rights_declaration(
    cur: Any,
    asset_id: int,
    profile_id: int,
    action_type: str,
    target_visibility: str,
    decl: dict[str, Any],
) -> int:
    """Schreibt einen neuen Eintrag in media_asset_rights_declarations (append-only).

    Returns: id des neuen Eintrags
    """
    cur.execute(
        """INSERT INTO media_asset_rights_declarations (
                media_asset_id, declared_by_profile_id, action_type, target_visibility,
                declaration_version,
                rights_holder_confirmed,
                contains_identifiable_persons, person_consent_confirmed, person_consent_context,
                contains_minors, parental_consent_confirmed, parental_consent_context,
                contains_music, music_rights_confirmed, music_rights_context,
                contains_third_party_content, third_party_rights_confirmed, third_party_rights_context
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
            RETURNING id""",
        (
            asset_id,
            profile_id,
            action_type,
            target_visibility,
            DECLARATION_VERSION,
            bool(decl.get("rights_holder_confirmed")),
            decl.get("contains_identifiable_persons"),
            decl.get("person_consent_confirmed"),
            _clean_context(decl.get("person_consent_context")),
            decl.get("contains_minors"),
            decl.get("parental_consent_confirmed"),
            _clean_context(decl.get("parental_consent_context")),
            decl.get("contains_music"),
            decl.get("music_rights_confirmed"),
            _clean_context(decl.get("music_rights_context")),
            decl.get("contains_third_party_content"),
            decl.get("third_party_rights_confirmed"),
            _clean_context(decl.get("third_party_rights_context")),
        ),
    )
    row = cur.fetchone()
    if hasattr(row, "keys"):
        return int(row["id"])
    return int(row[0])


def write_audit_log_entry(
    cur: Any,
    asset_id: int,
    acting_profile_id: Optional[int],
    event_type: str,
    old_values: dict,
    new_values: dict,
) -> None:
    """Schreibt einen Eintrag in media_asset_audit_log (append-only)."""
    cur.execute(
        """INSERT INTO media_asset_audit_log
               (media_asset_id, acting_profile_id, event_type, old_values, new_values)
           VALUES (%s, %s, %s, %s, %s)""",
        (
            asset_id,
            acting_profile_id,
            event_type,
            _json.dumps(old_values, default=str),
            _json.dumps(new_values, default=str),
        ),
    )


def write_rights_correction_declaration(
    cur: Any,
    asset_id: int,
    profile_id: int,
    target_visibility: str,
    decl: dict[str, Any],
    correction_note: Optional[str],
) -> int:
    """Schreibt eine Korrektur-Deklaration (action_type='correction', append-only)."""
    cur.execute(
        """INSERT INTO media_asset_rights_declarations (
                media_asset_id, declared_by_profile_id, action_type, target_visibility,
                declaration_version,
                rights_holder_confirmed,
                contains_identifiable_persons, person_consent_confirmed, person_consent_context,
                contains_minors, parental_consent_confirmed, parental_consent_context,
                contains_music, music_rights_confirmed, music_rights_context,
                contains_third_party_content, third_party_rights_confirmed, third_party_rights_context,
                correction_note
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
            RETURNING id""",
        (
            asset_id,
            profile_id,
            "correction",
            target_visibility,
            DECLARATION_VERSION,
            bool(decl.get("rights_holder_confirmed")),
            decl.get("contains_identifiable_persons"),
            decl.get("person_consent_confirmed"),
            _clean_context(decl.get("person_consent_context")),
            decl.get("contains_minors"),
            decl.get("parental_consent_confirmed"),
            _clean_context(decl.get("parental_consent_context")),
            decl.get("contains_music"),
            decl.get("music_rights_confirmed"),
            _clean_context(decl.get("music_rights_context")),
            decl.get("contains_third_party_content"),
            decl.get("third_party_rights_confirmed"),
            _clean_context(decl.get("third_party_rights_context")),
            _clean_context(correction_note),
        ),
    )
    row = cur.fetchone()
    if hasattr(row, "keys"):
        return int(row["id"])
    return int(row[0])


def update_rights_quick_fields(cur: Any, asset_id: int, target_visibility: str) -> None:
    """Setzt die Schnellfelder in media_assets nach erfolgreicher Deklaration."""
    cur.execute(
        """UPDATE media_assets
           SET rights_status = 'declared',
               rights_declared_for_visibility = %s,
               rights_declared_at = NOW(),
               updated_at = NOW()
           WHERE id = %s""",
        (target_visibility, asset_id),
    )
