"""
RBAC Policy Engine

Core engine for RBAC/ABAC permission checks,
role assignments, key shares, and default policies.
Extracted from rbac.py for file-size compliance.
"""

from typing import Optional, List, Dict, Set
from datetime import datetime, timezone
import uuid
from functools import wraps

from fastapi import HTTPException, Request

from .rbac_types import (
    Role,
    Action,
    ResourceType,
    ZKVisibilityMode,
    PolicySet,
    RoleAssignment,
    KeyShare,
)
from .rbac_permissions import DEFAULT_PERMISSIONS


# =============================================
# POLICY ENGINE
# =============================================

class PolicyEngine:
    """
    Engine fuer RBAC/ABAC Entscheidungen.

    Prueft:
    1. Basis-Rollenberechtigung (RBAC)
    2. Policy-Einschraenkungen (ABAC)
    3. Key Share Berechtigungen
    """

    def __init__(self):
        self.policy_sets: Dict[str, PolicySet] = {}
        self.role_assignments: Dict[str, List[RoleAssignment]] = {}  # user_id -> assignments
        self.key_shares: Dict[str, List[KeyShare]] = {}  # user_id -> shares

    def register_policy_set(self, policy: PolicySet):
        """Registriere ein Policy Set."""
        self.policy_sets[policy.id] = policy

    def get_policy_for_context(
        self,
        bundesland: str,
        jahr: int,
        fach: Optional[str] = None,
        verfahren: str = "abitur"
    ) -> Optional[PolicySet]:
        """Finde das passende Policy Set fuer einen Kontext."""
        # Exakte Uebereinstimmung
        for policy in self.policy_sets.values():
            if (policy.bundesland == bundesland and
                policy.jahr == jahr and
                policy.verfahren == verfahren):
                if policy.fach is None or policy.fach == fach:
                    return policy

        # Fallback: Default Policy
        for policy in self.policy_sets.values():
            if policy.bundesland == "DEFAULT":
                return policy

        return None

    def assign_role(
        self,
        user_id: str,
        role: Role,
        resource_type: ResourceType,
        resource_id: str,
        granted_by: str,
        tenant_id: Optional[str] = None,
        namespace_id: Optional[str] = None,
        valid_to: Optional[datetime] = None
    ) -> RoleAssignment:
        """Weise einem User eine Rolle zu."""
        assignment = RoleAssignment(
            id=str(uuid.uuid4()),
            user_id=user_id,
            role=role,
            resource_type=resource_type,
            resource_id=resource_id,
            tenant_id=tenant_id,
            namespace_id=namespace_id,
            granted_by=granted_by,
            valid_to=valid_to
        )

        if user_id not in self.role_assignments:
            self.role_assignments[user_id] = []
        self.role_assignments[user_id].append(assignment)

        return assignment

    def revoke_role(self, assignment_id: str, revoked_by: str) -> bool:
        """Widerrufe eine Rollenzuweisung."""
        for user_assignments in self.role_assignments.values():
            for assignment in user_assignments:
                if assignment.id == assignment_id:
                    assignment.revoked_at = datetime.now(timezone.utc)
                    return True
        return False

    def get_user_roles(
        self,
        user_id: str,
        resource_type: Optional[ResourceType] = None,
        resource_id: Optional[str] = None
    ) -> List[Role]:
        """Hole alle aktiven Rollen eines Users."""
        assignments = self.role_assignments.get(user_id, [])
        roles = []

        for assignment in assignments:
            if not assignment.is_active():
                continue
            if resource_type and assignment.resource_type != resource_type:
                continue
            if resource_id and assignment.resource_id != resource_id:
                continue
            roles.append(assignment.role)

        return list(set(roles))

    def create_key_share(
        self,
        user_id: str,
        package_id: str,
        permissions: Set[str],
        granted_by: str,
        scope: str = "full",
        invite_token: Optional[str] = None
    ) -> KeyShare:
        """Erstelle einen Key Share."""
        share = KeyShare(
            id=str(uuid.uuid4()),
            user_id=user_id,
            package_id=package_id,
            permissions=permissions,
            scope=scope,
            granted_by=granted_by,
            invite_token=invite_token
        )

        if user_id not in self.key_shares:
            self.key_shares[user_id] = []
        self.key_shares[user_id].append(share)

        return share

    def accept_key_share(self, share_id: str, token: str) -> bool:
        """Akzeptiere einen Key Share via Invite Token."""
        for user_shares in self.key_shares.values():
            for share in user_shares:
                if share.id == share_id and share.invite_token == token:
                    share.accepted_at = datetime.now(timezone.utc)
                    return True
        return False

    def revoke_key_share(self, share_id: str, revoked_by: str) -> bool:
        """Widerrufe einen Key Share."""
        for user_shares in self.key_shares.values():
            for share in user_shares:
                if share.id == share_id:
                    share.revoked_at = datetime.now(timezone.utc)
                    share.revoked_by = revoked_by
                    return True
        return False

    def check_permission(
        self,
        user_id: str,
        action: Action,
        resource_type: ResourceType,
        resource_id: str,
        policy: Optional[PolicySet] = None,
        package_id: Optional[str] = None
    ) -> bool:
        """
        Pruefe ob ein User eine Aktion ausfuehren darf.

        Prueft:
        1. Basis-RBAC
        2. Policy-Einschraenkungen
        3. Key Share (falls package_id angegeben)
        """
        # 1. Hole aktive Rollen
        roles = self.get_user_roles(user_id, resource_type, resource_id)

        if not roles:
            return False

        # 2. Pruefe Basis-RBAC
        has_permission = False
        for role in roles:
            role_permissions = DEFAULT_PERMISSIONS.get(role, {})
            resource_permissions = role_permissions.get(resource_type, set())
            if action in resource_permissions:
                has_permission = True
                break

        if not has_permission:
            return False

        # 3. Pruefe Policy-Einschraenkungen
        if policy:
            # ZK Visibility Mode
            if Role.ZWEITKORREKTOR in roles:
                if policy.zk_visibility_mode == ZKVisibilityMode.BLIND:
                    # Blind: ZK darf EK-Outputs nicht sehen
                    if resource_type in [ResourceType.EVALUATION, ResourceType.REPORT, ResourceType.GRADE_DECISION]:
                        if action == Action.READ:
                            # Pruefe ob es EK-Outputs sind (muesste ueber Metadaten geprueft werden)
                            pass  # Implementierung abhaengig von Datenmodell

                elif policy.zk_visibility_mode == ZKVisibilityMode.SEMI:
                    # Semi: ZK sieht Annotationen, aber keine Note
                    if resource_type == ResourceType.GRADE_DECISION and action == Action.READ:
                        return False

        # 4. Pruefe Key Share (falls Package-basiert)
        if package_id:
            user_shares = self.key_shares.get(user_id, [])
            has_key_share = any(
                share.package_id == package_id and share.is_active()
                for share in user_shares
            )
            if not has_key_share:
                return False

        return True

    def get_allowed_actions(
        self,
        user_id: str,
        resource_type: ResourceType,
        resource_id: str,
        policy: Optional[PolicySet] = None
    ) -> Set[Action]:
        """Hole alle erlaubten Aktionen fuer einen User auf einer Ressource."""
        roles = self.get_user_roles(user_id, resource_type, resource_id)
        allowed = set()

        for role in roles:
            role_permissions = DEFAULT_PERMISSIONS.get(role, {})
            resource_permissions = role_permissions.get(resource_type, set())
            allowed.update(resource_permissions)

        # Policy-Einschraenkungen anwenden
        if policy and Role.ZWEITKORREKTOR in roles:
            if policy.zk_visibility_mode == ZKVisibilityMode.BLIND:
                # Entferne READ fuer bestimmte Ressourcen
                pass  # Detailimplementierung

        return allowed


# =============================================
# DEFAULT POLICY SETS (alle Bundeslaender)
# =============================================

def create_default_policy_sets() -> List[PolicySet]:
    """
    Erstelle Default Policy Sets fuer alle Bundeslaender.

    Diese koennen spaeter pro Land verfeinert werden.
    """
    bundeslaender = [
        "baden-wuerttemberg", "bayern", "berlin", "brandenburg",
        "bremen", "hamburg", "hessen", "mecklenburg-vorpommern",
        "niedersachsen", "nordrhein-westfalen", "rheinland-pfalz",
        "saarland", "sachsen", "sachsen-anhalt", "schleswig-holstein",
        "thueringen"
    ]

    policies = []

    # Default Policy (Fallback)
    policies.append(PolicySet(
        id="DEFAULT-2025",
        bundesland="DEFAULT",
        jahr=2025,
        fach=None,
        verfahren="abitur",
        zk_visibility_mode=ZKVisibilityMode.FULL,
        eh_visibility_mode=PolicySet.__dataclass_fields__["eh_visibility_mode"].default,
        allow_teacher_uploaded_eh=True,
        allow_land_uploaded_eh=True,
        require_rights_confirmation_on_upload=True,
        third_correction_threshold=4,
        final_signoff_role="fachvorsitz"
    ))

    # Niedersachsen (Beispiel mit spezifischen Anpassungen)
    policies.append(PolicySet(
        id="NI-2025-ABITUR",
        bundesland="niedersachsen",
        jahr=2025,
        fach=None,
        verfahren="abitur",
        zk_visibility_mode=ZKVisibilityMode.FULL,  # In NI sieht ZK alles
        allow_teacher_uploaded_eh=True,
        allow_land_uploaded_eh=True,
        require_rights_confirmation_on_upload=True,
        third_correction_threshold=4,
        final_signoff_role="fachvorsitz",
        export_template_id="niedersachsen-abitur"
    ))

    # Bayern (Beispiel mit SEMI visibility)
    policies.append(PolicySet(
        id="BY-2025-ABITUR",
        bundesland="bayern",
        jahr=2025,
        fach=None,
        verfahren="abitur",
        zk_visibility_mode=ZKVisibilityMode.SEMI,  # ZK sieht Annotationen, nicht Note
        allow_teacher_uploaded_eh=True,
        allow_land_uploaded_eh=True,
        require_rights_confirmation_on_upload=True,
        third_correction_threshold=4,
        final_signoff_role="fachvorsitz",
        export_template_id="bayern-abitur"
    ))

    # NRW (Beispiel)
    policies.append(PolicySet(
        id="NW-2025-ABITUR",
        bundesland="nordrhein-westfalen",
        jahr=2025,
        fach=None,
        verfahren="abitur",
        zk_visibility_mode=ZKVisibilityMode.FULL,
        allow_teacher_uploaded_eh=True,
        allow_land_uploaded_eh=True,
        require_rights_confirmation_on_upload=True,
        third_correction_threshold=4,
        final_signoff_role="fachvorsitz",
        export_template_id="nrw-abitur"
    ))

    # Generiere Basis-Policies fuer alle anderen Bundeslaender
    for bl in bundeslaender:
        if bl not in ["niedersachsen", "bayern", "nordrhein-westfalen"]:
            policies.append(PolicySet(
                id=f"{bl[:2].upper()}-2025-ABITUR",
                bundesland=bl,
                jahr=2025,
                fach=None,
                verfahren="abitur",
                zk_visibility_mode=ZKVisibilityMode.FULL,
                allow_teacher_uploaded_eh=True,
                allow_land_uploaded_eh=True,
                require_rights_confirmation_on_upload=True,
                third_correction_threshold=4,
                final_signoff_role="fachvorsitz"
            ))

    return policies


# =============================================
# GLOBAL POLICY ENGINE INSTANCE
# =============================================

# Singleton Policy Engine
_policy_engine: Optional[PolicyEngine] = None


def get_policy_engine() -> PolicyEngine:
    """Hole die globale Policy Engine Instanz."""
    global _policy_engine
    if _policy_engine is None:
        _policy_engine = PolicyEngine()
        # Registriere Default Policies
        for policy in create_default_policy_sets():
            _policy_engine.register_policy_set(policy)
    return _policy_engine


# =============================================
# API GUARDS (Decorators fuer FastAPI)
# =============================================

def require_permission(
    action: Action,
    resource_type: ResourceType,
    resource_id_param: str = "resource_id"
):
    """
    Decorator fuer FastAPI Endpoints.

    Prueft ob der aktuelle User die angegebene Berechtigung hat.

    Usage:
        @app.get("/api/v1/packages/{package_id}")
        @require_permission(Action.READ, ResourceType.EXAM_PACKAGE, "package_id")
        async def get_package(package_id: str, request: Request):
            ...
    """
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            request = kwargs.get('request')
            if not request:
                for arg in args:
                    if isinstance(arg, Request):
                        request = arg
                        break

            if not request:
                raise HTTPException(status_code=500, detail="Request not found")

            # User aus Token holen
            user = getattr(request.state, 'user', None)
            if not user:
                raise HTTPException(status_code=401, detail="Not authenticated")

            user_id = user.get('user_id')
            resource_id = kwargs.get(resource_id_param)

            # Policy Engine pruefen
            engine = get_policy_engine()

            # Optional: Policy aus Kontext laden
            policy = None
            bundesland = user.get('bundesland')
            if bundesland:
                policy = engine.get_policy_for_context(bundesland, 2025)

            if not engine.check_permission(
                user_id=user_id,
                action=action,
                resource_type=resource_type,
                resource_id=resource_id,
                policy=policy
            ):
                raise HTTPException(
                    status_code=403,
                    detail=f"Permission denied: {action.value} on {resource_type.value}"
                )

            return await func(*args, **kwargs)

        return wrapper
    return decorator


def require_role(role: Role):
    """
    Decorator der prueft ob User eine bestimmte Rolle hat.

    Usage:
        @app.post("/api/v1/eh/publish")
        @require_role(Role.LAND_ADMIN)
        async def publish_eh(request: Request):
            ...
    """
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            request = kwargs.get('request')
            if not request:
                for arg in args:
                    if isinstance(arg, Request):
                        request = arg
                        break

            if not request:
                raise HTTPException(status_code=500, detail="Request not found")

            user = getattr(request.state, 'user', None)
            if not user:
                raise HTTPException(status_code=401, detail="Not authenticated")

            user_id = user.get('user_id')
            engine = get_policy_engine()

            user_roles = engine.get_user_roles(user_id)
            if role not in user_roles:
                raise HTTPException(
                    status_code=403,
                    detail=f"Role required: {role.value}"
                )

            return await func(*args, **kwargs)

        return wrapper
    return decorator
