"""
RBAC/ABAC Type Definitions

Enums, data structures, and models for the policy system.
Extracted from rbac.py for file-size compliance.
"""

import json
from enum import Enum
from dataclasses import dataclass, field, asdict
from typing import Optional, List, Dict, Set, Any
from datetime import datetime, timezone
import uuid


# =============================================
# ENUMS: Roles, Actions, Resources
# =============================================

class Role(str, Enum):
    """Fachliche Rollen in Korrektur- und Zeugniskette."""

    # === Klausur-Korrekturkette ===
    ERSTKORREKTOR = "erstkorrektor"      # EK
    ZWEITKORREKTOR = "zweitkorrektor"    # ZK
    DRITTKORREKTOR = "drittkorrektor"    # DK

    # === Zeugnis-Workflow ===
    KLASSENLEHRER = "klassenlehrer"      # KL - Erstellt Zeugnis, Kopfnoten, Bemerkungen
    FACHLEHRER = "fachlehrer"            # FL - Traegt Fachnoten ein
    ZEUGNISBEAUFTRAGTER = "zeugnisbeauftragter"  # ZB - Qualitaetskontrolle
    SEKRETARIAT = "sekretariat"          # SEK - Druck, Versand, Archivierung

    # === Leitung (Klausur + Zeugnis) ===
    FACHVORSITZ = "fachvorsitz"          # FVL - Fachpruefungsleitung
    PRUEFUNGSVORSITZ = "pruefungsvorsitz" # PV - Schulleitung / Pruefungsvorsitz
    SCHULLEITUNG = "schulleitung"        # SL - Finale Zeugnis-Freigabe
    STUFENLEITUNG = "stufenleitung"      # STL - Stufenkoordination

    # === Administration ===
    SCHUL_ADMIN = "schul_admin"          # SA
    LAND_ADMIN = "land_admin"            # LA - Behoerde

    # === Spezial ===
    AUDITOR = "auditor"                  # DSB/Auditor
    OPERATOR = "operator"                # OPS - Support
    TEACHER_ASSISTANT = "teacher_assistant"  # TA - Referendar
    EXAM_AUTHOR = "exam_author"          # EA - nur Vorabi


class Action(str, Enum):
    """Moegliche Operationen auf Ressourcen."""
    CREATE = "create"
    READ = "read"
    UPDATE = "update"
    DELETE = "delete"

    ASSIGN_ROLE = "assign_role"
    INVITE_USER = "invite_user"
    REMOVE_USER = "remove_user"

    UPLOAD = "upload"
    DOWNLOAD = "download"

    LOCK = "lock"              # Finalisieren
    UNLOCK = "unlock"          # Nur mit Sonderrecht
    SIGN_OFF = "sign_off"      # Freigabe

    SHARE_KEY = "share_key"    # Key Share erzeugen
    VIEW_PII = "view_pii"      # Falls PII vorhanden
    BREAK_GLASS = "break_glass"  # Notfallzugriff

    PUBLISH_OFFICIAL = "publish_official"  # Amtliche EH verteilen


class ResourceType(str, Enum):
    """Ressourcentypen im System."""
    TENANT = "tenant"
    NAMESPACE = "namespace"

    # === Klausur-Korrektur ===
    EXAM_PACKAGE = "exam_package"
    STUDENT_WORK = "student_work"
    EH_DOCUMENT = "eh_document"
    RUBRIC = "rubric"              # Punkteraster
    ANNOTATION = "annotation"
    EVALUATION = "evaluation"      # Kriterien/Punkte
    REPORT = "report"              # Gutachten
    GRADE_DECISION = "grade_decision"

    # === Zeugnisgenerator ===
    ZEUGNIS = "zeugnis"                        # Zeugnisdokument
    ZEUGNIS_VORLAGE = "zeugnis_vorlage"        # Zeugnisvorlage/Template
    ZEUGNIS_ENTWURF = "zeugnis_entwurf"        # Zeugnisentwurf (vor Freigabe)
    SCHUELER_DATEN = "schueler_daten"          # Schueler-Stammdaten, Noten
    FACHNOTE = "fachnote"                      # Einzelne Fachnote
    KOPFNOTE = "kopfnote"                      # Arbeits-/Sozialverhalten
    FEHLZEITEN = "fehlzeiten"                  # Fehlzeiten
    BEMERKUNG = "bemerkung"                    # Zeugnisbemerkungen
    KONFERENZ_BESCHLUSS = "konferenz_beschluss"  # Konferenzergebnis
    VERSETZUNG = "versetzung"                  # Versetzungsentscheidung

    # === Allgemein ===
    DOCUMENT = "document"          # Generischer Dokumenttyp (EH, Vorlagen, etc.)
    TEMPLATE = "template"          # Generische Vorlagen
    EXPORT = "export"
    AUDIT_LOG = "audit_log"
    KEY_MATERIAL = "key_material"


class ZKVisibilityMode(str, Enum):
    """Sichtbarkeitsmodus fuer Zweitkorrektoren."""
    BLIND = "blind"    # ZK sieht keine EK-Note/Gutachten
    SEMI = "semi"      # ZK sieht Annotationen, aber keine Note
    FULL = "full"      # ZK sieht alles


class EHVisibilityMode(str, Enum):
    """Sichtbarkeitsmodus fuer Erwartungshorizonte."""
    BLIND = "blind"    # ZK sieht EH nicht (selten)
    SHARED = "shared"  # ZK sieht EH (Standard)


class VerfahrenType(str, Enum):
    """Verfahrenstypen fuer Klausuren und Zeugnisse."""

    # === Klausur/Pruefungsverfahren ===
    ABITUR = "abitur"
    VORABITUR = "vorabitur"
    KLAUSUR = "klausur"
    NACHPRUEFUNG = "nachpruefung"

    # === Zeugnisverfahren ===
    HALBJAHRESZEUGNIS = "halbjahreszeugnis"
    JAHRESZEUGNIS = "jahreszeugnis"
    ABSCHLUSSZEUGNIS = "abschlusszeugnis"
    ABGANGSZEUGNIS = "abgangszeugnis"

    @classmethod
    def is_exam_type(cls, verfahren: str) -> bool:
        """Pruefe ob Verfahren ein Pruefungstyp ist."""
        exam_types = {cls.ABITUR, cls.VORABITUR, cls.KLAUSUR, cls.NACHPRUEFUNG}
        try:
            return cls(verfahren) in exam_types
        except ValueError:
            return False

    @classmethod
    def is_certificate_type(cls, verfahren: str) -> bool:
        """Pruefe ob Verfahren ein Zeugnistyp ist."""
        cert_types = {cls.HALBJAHRESZEUGNIS, cls.JAHRESZEUGNIS, cls.ABSCHLUSSZEUGNIS, cls.ABGANGSZEUGNIS}
        try:
            return cls(verfahren) in cert_types
        except ValueError:
            return False


# =============================================
# DATA STRUCTURES
# =============================================

@dataclass
class PolicySet:
    """
    Policy-Konfiguration pro Bundesland/Jahr/Fach.

    Ermoeglicht bundesland-spezifische Unterschiede ohne
    harte Codierung im Quellcode.

    Unterstuetzte Verfahrenstypen:
    - Pruefungen: abitur, vorabitur, klausur, nachpruefung
    - Zeugnisse: halbjahreszeugnis, jahreszeugnis, abschlusszeugnis, abgangszeugnis
    """
    id: str
    bundesland: str
    jahr: int
    fach: Optional[str]  # None = gilt fuer alle Faecher
    verfahren: str       # See VerfahrenType enum

    # Sichtbarkeitsregeln (Klausur)
    zk_visibility_mode: ZKVisibilityMode = ZKVisibilityMode.FULL
    eh_visibility_mode: EHVisibilityMode = EHVisibilityMode.SHARED

    # EH-Quellen (Klausur)
    allow_teacher_uploaded_eh: bool = True
    allow_land_uploaded_eh: bool = True
    require_rights_confirmation_on_upload: bool = True
    require_dual_control_for_official_eh_update: bool = False

    # Korrekturregeln (Klausur)
    third_correction_threshold: int = 4  # Notenpunkte Abweichung
    final_signoff_role: str = "fachvorsitz"

    # Zeugnisregeln (Zeugnis)
    require_klassenlehrer_approval: bool = True
    require_schulleitung_signoff: bool = True
    allow_sekretariat_edit_after_approval: bool = False
    konferenz_protokoll_required: bool = True
    bemerkungen_require_review: bool = True
    fehlzeiten_auto_import: bool = True
    kopfnoten_enabled: bool = False
    versetzung_auto_calculate: bool = True

    # Export & Anzeige
    quote_verbatim_allowed: bool = False  # Amtliche Texte in UI
    export_template_id: str = "default"

    # Zusaetzliche Flags
    flags: Dict[str, Any] = field(default_factory=dict)

    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))

    def is_exam_policy(self) -> bool:
        """Pruefe ob diese Policy fuer Pruefungen ist."""
        return VerfahrenType.is_exam_type(self.verfahren)

    def is_certificate_policy(self) -> bool:
        """Pruefe ob diese Policy fuer Zeugnisse ist."""
        return VerfahrenType.is_certificate_type(self.verfahren)

    def to_dict(self):
        d = asdict(self)
        d['zk_visibility_mode'] = self.zk_visibility_mode.value
        d['eh_visibility_mode'] = self.eh_visibility_mode.value
        d['created_at'] = self.created_at.isoformat()
        return d


@dataclass
class RoleAssignment:
    """
    Zuweisung einer Rolle zu einem User fuer eine spezifische Ressource.
    """
    id: str
    user_id: str
    role: Role
    resource_type: ResourceType
    resource_id: str

    # Optionale Einschraenkungen
    tenant_id: Optional[str] = None
    namespace_id: Optional[str] = None

    # Gueltigkeit
    valid_from: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    valid_to: Optional[datetime] = None

    # Metadaten
    granted_by: str = ""
    granted_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    revoked_at: Optional[datetime] = None

    def is_active(self) -> bool:
        now = datetime.now(timezone.utc)
        if self.revoked_at:
            return False
        if self.valid_to and now > self.valid_to:
            return False
        return now >= self.valid_from

    def to_dict(self):
        return {
            'id': self.id,
            'user_id': self.user_id,
            'role': self.role.value,
            'resource_type': self.resource_type.value,
            'resource_id': self.resource_id,
            'tenant_id': self.tenant_id,
            'namespace_id': self.namespace_id,
            'valid_from': self.valid_from.isoformat(),
            'valid_to': self.valid_to.isoformat() if self.valid_to else None,
            'granted_by': self.granted_by,
            'granted_at': self.granted_at.isoformat(),
            'revoked_at': self.revoked_at.isoformat() if self.revoked_at else None,
            'is_active': self.is_active()
        }


@dataclass
class KeyShare:
    """
    Berechtigung fuer einen User, auf verschluesselte Inhalte zuzugreifen.

    Ein KeyShare ist KEIN Schluessel im Klartext, sondern eine
    Berechtigung in Verbindung mit Role Assignment.
    """
    id: str
    user_id: str
    package_id: str

    # Berechtigungsumfang
    permissions: Set[str] = field(default_factory=set)
    # z.B. {"read_original", "read_eh", "read_ek_outputs", "write_annotations"}

    # Optionale Einschraenkungen
    scope: str = "full"  # "full", "original_only", "eh_only", "outputs_only"

    # Kette
    granted_by: str = ""
    granted_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))

    # Akzeptanz (fuer Invite-Flow)
    invite_token: Optional[str] = None
    accepted_at: Optional[datetime] = None

    # Widerruf
    revoked_at: Optional[datetime] = None
    revoked_by: Optional[str] = None

    def is_active(self) -> bool:
        return self.revoked_at is None and (
            self.invite_token is None or self.accepted_at is not None
        )

    def to_dict(self):
        return {
            'id': self.id,
            'user_id': self.user_id,
            'package_id': self.package_id,
            'permissions': list(self.permissions),
            'scope': self.scope,
            'granted_by': self.granted_by,
            'granted_at': self.granted_at.isoformat(),
            'invite_token': self.invite_token,
            'accepted_at': self.accepted_at.isoformat() if self.accepted_at else None,
            'revoked_at': self.revoked_at.isoformat() if self.revoked_at else None,
            'is_active': self.is_active()
        }


@dataclass
class Tenant:
    """
    Hoechste Isolationseinheit - typischerweise eine Schule.
    """
    id: str
    name: str
    bundesland: str
    tenant_type: str = "school"  # "school", "pruefungszentrum", "behoerde"

    # Verschluesselung
    encryption_enabled: bool = True

    # Metadaten
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    deleted_at: Optional[datetime] = None

    def to_dict(self):
        return {
            'id': self.id,
            'name': self.name,
            'bundesland': self.bundesland,
            'tenant_type': self.tenant_type,
            'encryption_enabled': self.encryption_enabled,
            'created_at': self.created_at.isoformat()
        }


@dataclass
class Namespace:
    """
    Arbeitsraum innerhalb eines Tenants.
    z.B. "Abitur 2026 - Deutsch LK - Kurs 12a"
    """
    id: str
    tenant_id: str
    name: str

    # Kontext
    jahr: int
    fach: str
    kurs: Optional[str] = None
    pruefungsart: str = "abitur"  # "abitur", "vorabitur"

    # Policy
    policy_set_id: Optional[str] = None

    # Metadaten
    created_by: str = ""
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    deleted_at: Optional[datetime] = None

    def to_dict(self):
        return {
            'id': self.id,
            'tenant_id': self.tenant_id,
            'name': self.name,
            'jahr': self.jahr,
            'fach': self.fach,
            'kurs': self.kurs,
            'pruefungsart': self.pruefungsart,
            'policy_set_id': self.policy_set_id,
            'created_by': self.created_by,
            'created_at': self.created_at.isoformat()
        }


@dataclass
class ExamPackage:
    """
    Pruefungspaket - kompletter Satz Arbeiten mit allen Artefakten.
    """
    id: str
    namespace_id: str
    tenant_id: str

    name: str
    beschreibung: Optional[str] = None

    # Workflow-Status
    status: str = "draft"  # "draft", "in_progress", "locked", "signed_off"

    # Beteiligte (Rollen werden separat zugewiesen)
    owner_id: str = ""  # Typischerweise EK

    # Verschluesselung
    encryption_key_id: Optional[str] = None

    # Timestamps
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    locked_at: Optional[datetime] = None
    signed_off_at: Optional[datetime] = None
    signed_off_by: Optional[str] = None

    def to_dict(self):
        return {
            'id': self.id,
            'namespace_id': self.namespace_id,
            'tenant_id': self.tenant_id,
            'name': self.name,
            'beschreibung': self.beschreibung,
            'status': self.status,
            'owner_id': self.owner_id,
            'created_at': self.created_at.isoformat(),
            'locked_at': self.locked_at.isoformat() if self.locked_at else None,
            'signed_off_at': self.signed_off_at.isoformat() if self.signed_off_at else None,
            'signed_off_by': self.signed_off_by
        }
