"""
RBAC API - Role and Assignment Management Endpoints

Provides API endpoints for:
- Listing all available roles (built-in + custom)
- Assigning/revoking roles to users
- Role summary with assignment counts
- Custom role CRUD

Shared infrastructure (DB pool, Pydantic models, role definitions)
used by rbac_teachers_api.py as well.

Architecture:
- Authentication: Keycloak (when configured) or local JWT
- Authorization: Custom rbac.py for fine-grained permissions
"""

import os
import asyncpg
from datetime import datetime, timezone
from typing import Optional, List, Dict, Any
from fastapi import APIRouter, HTTPException, Depends, Request
from pydantic import BaseModel

# Import hybrid auth module
try:
    from auth import get_current_user, TokenExpiredError, TokenInvalidError
except ImportError:
    # Fallback for standalone testing
    from auth.keycloak_auth import TokenExpiredError, TokenInvalidError
    from auth.dependencies import get_current_user

# Configuration from environment - NO DEFAULT SECRETS
ENVIRONMENT = os.environ.get("ENVIRONMENT", "development")

router = APIRouter(prefix="/rbac", tags=["rbac"])

# Connection pool
_pool: Optional[asyncpg.Pool] = None


def _get_database_url() -> str:
    """Get DATABASE_URL from environment, raising error if not set."""
    url = os.environ.get("DATABASE_URL")
    if not url:
        raise RuntimeError("DATABASE_URL nicht konfiguriert - bitte via Vault oder Umgebungsvariable setzen")
    return url


async def get_pool() -> asyncpg.Pool:
    """Get or create database connection pool"""
    global _pool
    if _pool is None:
        database_url = _get_database_url()
        _pool = await asyncpg.create_pool(database_url, min_size=2, max_size=10)
    return _pool


async def close_pool():
    """Close database connection pool"""
    global _pool
    if _pool:
        await _pool.close()
        _pool = None


# Pydantic Models
class RoleAssignmentCreate(BaseModel):
    user_id: str
    role: str
    resource_type: str = "tenant"
    resource_id: str
    valid_to: Optional[str] = None


class RoleAssignmentRevoke(BaseModel):
    assignment_id: str


class TeacherCreate(BaseModel):
    email: str
    first_name: str
    last_name: str
    teacher_code: Optional[str] = None
    title: Optional[str] = None
    roles: List[str] = []


class TeacherUpdate(BaseModel):
    email: Optional[str] = None
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    teacher_code: Optional[str] = None
    title: Optional[str] = None
    is_active: Optional[bool] = None


class CustomRoleCreate(BaseModel):
    role_key: str
    display_name: str
    description: str
    category: str


class CustomRoleUpdate(BaseModel):
    display_name: Optional[str] = None
    description: Optional[str] = None
    category: Optional[str] = None


class TeacherResponse(BaseModel):
    id: str
    user_id: str
    email: str
    name: str
    teacher_code: Optional[str]
    title: Optional[str]
    first_name: str
    last_name: str
    is_active: bool
    roles: List[str]


class RoleInfo(BaseModel):
    role: str
    display_name: str
    description: str
    category: str


class RoleAssignmentResponse(BaseModel):
    id: str
    user_id: str
    role: str
    resource_type: str
    resource_id: str
    valid_from: str
    valid_to: Optional[str]
    granted_at: str
    is_active: bool


# Role definitions with German display names
AVAILABLE_ROLES = {
    # Klausur-Korrekturkette
    "erstkorrektor": {
        "display_name": "Erstkorrektor",
        "description": "Fuehrt die erste Korrektur der Klausur durch",
        "category": "klausur"
    },
    "zweitkorrektor": {
        "display_name": "Zweitkorrektor",
        "description": "Fuehrt die zweite Korrektur der Klausur durch",
        "category": "klausur"
    },
    "drittkorrektor": {
        "display_name": "Drittkorrektor",
        "description": "Fuehrt die dritte Korrektur bei Notenabweichung durch",
        "category": "klausur"
    },
    # Zeugnis-Workflow
    "klassenlehrer": {
        "display_name": "Klassenlehrer/in",
        "description": "Erstellt Zeugnisse, traegt Kopfnoten und Bemerkungen ein",
        "category": "zeugnis"
    },
    "fachlehrer": {
        "display_name": "Fachlehrer/in",
        "description": "Traegt Fachnoten ein",
        "category": "zeugnis"
    },
    "zeugnisbeauftragter": {
        "display_name": "Zeugnisbeauftragte/r",
        "description": "Qualitaetskontrolle und Freigabe von Zeugnissen",
        "category": "zeugnis"
    },
    "sekretariat": {
        "display_name": "Sekretariat",
        "description": "Druck, Versand und Archivierung von Dokumenten",
        "category": "verwaltung"
    },
    # Leitung
    "fachvorsitz": {
        "display_name": "Fachvorsitz",
        "description": "Fachpruefungsleitung und Qualitaetssicherung",
        "category": "leitung"
    },
    "pruefungsvorsitz": {
        "display_name": "Pruefungsvorsitz",
        "description": "Pruefungsleitung und finale Freigabe",
        "category": "leitung"
    },
    "schulleitung": {
        "display_name": "Schulleitung",
        "description": "Finale Freigabe und Unterschrift",
        "category": "leitung"
    },
    "stufenleitung": {
        "display_name": "Stufenleitung",
        "description": "Koordination einer Jahrgangsstufe",
        "category": "leitung"
    },
    # Administration
    "schul_admin": {
        "display_name": "Schul-Administrator",
        "description": "Technische Administration der Schule",
        "category": "admin"
    },
    "teacher_assistant": {
        "display_name": "Referendar/in",
        "description": "Lehrkraft in Ausbildung mit eingeschraenkten Rechten",
        "category": "other"
    },
}


# Note: get_user_from_token is replaced by the imported get_current_user dependency
# from auth module which supports both Keycloak and local JWT authentication


# API Endpoints

@router.get("/roles")
async def list_available_roles() -> List[RoleInfo]:
    """List all available roles with their descriptions"""
    return [
        RoleInfo(
            role=role_key,
            display_name=role_data["display_name"],
            description=role_data["description"],
            category=role_data["category"]
        )
        for role_key, role_data in AVAILABLE_ROLES.items()
    ]


@router.post("/assignments")
async def assign_role(assignment: RoleAssignmentCreate, user: Dict[str, Any] = Depends(get_current_user)) -> RoleAssignmentResponse:
    """Assign a role to a user"""
    if assignment.role not in AVAILABLE_ROLES:
        raise HTTPException(status_code=400, detail=f"Unknown role: {assignment.role}")

    pool = await get_pool()

    async with pool.acquire() as conn:
        # Check if assignment already exists
        existing = await conn.fetchrow("""
            SELECT id FROM role_assignments
            WHERE user_id = $1 AND role = $2 AND resource_id = $3
              AND revoked_at IS NULL
        """, assignment.user_id, assignment.role, assignment.resource_id)

        if existing:
            raise HTTPException(
                status_code=409,
                detail="Role assignment already exists"
            )

        # Parse valid_to if provided
        valid_to = None
        if assignment.valid_to:
            valid_to = datetime.fromisoformat(assignment.valid_to)

        # Create assignment
        result = await conn.fetchrow("""
            INSERT INTO role_assignments
                (user_id, role, resource_type, resource_id, tenant_id, valid_to, granted_by)
            VALUES ($1, $2, $3, $4, $5, $6, $7)
            RETURNING id, user_id, role, resource_type, resource_id, valid_from, valid_to, granted_at
        """,
            assignment.user_id,
            assignment.role,
            assignment.resource_type,
            assignment.resource_id,
            assignment.resource_id,  # tenant_id same as resource_id for tenant-level roles
            valid_to,
            user.get("user_id")
        )

        return RoleAssignmentResponse(
            id=str(result["id"]),
            user_id=str(result["user_id"]),
            role=result["role"],
            resource_type=result["resource_type"],
            resource_id=str(result["resource_id"]),
            valid_from=result["valid_from"].isoformat(),
            valid_to=result["valid_to"].isoformat() if result["valid_to"] else None,
            granted_at=result["granted_at"].isoformat(),
            is_active=True
        )


@router.delete("/assignments/{assignment_id}")
async def revoke_role(assignment_id: str, user: Dict[str, Any] = Depends(get_current_user)):
    """Revoke a role assignment"""
    pool = await get_pool()

    async with pool.acquire() as conn:
        result = await conn.execute("""
            UPDATE role_assignments
            SET revoked_at = NOW()
            WHERE id = $1 AND revoked_at IS NULL
        """, assignment_id)

        if result == "UPDATE 0":
            raise HTTPException(status_code=404, detail="Assignment not found or already revoked")

        return {"status": "revoked", "assignment_id": assignment_id}


@router.get("/summary")
async def get_role_summary(user: Dict[str, Any] = Depends(get_current_user)) -> Dict[str, Any]:
    """Get a summary of roles and their assignment counts"""
    pool = await get_pool()

    async with pool.acquire() as conn:
        counts = await conn.fetch("""
            SELECT role, COUNT(*) as count
            FROM role_assignments
            WHERE tenant_id = 'a0000000-0000-0000-0000-000000000001'
              AND revoked_at IS NULL
              AND (valid_to IS NULL OR valid_to > NOW())
            GROUP BY role
            ORDER BY role
        """)

        total_teachers = await conn.fetchval("""
            SELECT COUNT(*) FROM teachers
            WHERE school_id = 'a0000000-0000-0000-0000-000000000001'
              AND is_active = true
        """)

        role_counts = {c["role"]: c["count"] for c in counts}

        # Also include custom roles from database
        custom_roles = await conn.fetch("""
            SELECT role_key, display_name, category
            FROM custom_roles
            WHERE tenant_id = 'a0000000-0000-0000-0000-000000000001'
              AND is_active = true
        """)

        all_roles = [
            {
                "role": role_key,
                "display_name": role_data["display_name"],
                "category": role_data["category"],
                "count": role_counts.get(role_key, 0),
                "is_custom": False
            }
            for role_key, role_data in AVAILABLE_ROLES.items()
        ]

        for cr in custom_roles:
            all_roles.append({
                "role": cr["role_key"],
                "display_name": cr["display_name"],
                "category": cr["category"],
                "count": role_counts.get(cr["role_key"], 0),
                "is_custom": True
            })

        return {
            "total_teachers": total_teachers,
            "roles": all_roles
        }


# ==========================================
# CUSTOM ROLE MANAGEMENT ENDPOINTS
# ==========================================

@router.get("/custom-roles")
async def list_custom_roles(user: Dict[str, Any] = Depends(get_current_user)) -> List[RoleInfo]:
    """List all custom roles"""
    pool = await get_pool()

    async with pool.acquire() as conn:
        roles = await conn.fetch("""
            SELECT role_key, display_name, description, category
            FROM custom_roles
            WHERE tenant_id = 'a0000000-0000-0000-0000-000000000001'
              AND is_active = true
            ORDER BY category, display_name
        """)

        return [
            RoleInfo(
                role=r["role_key"],
                display_name=r["display_name"],
                description=r["description"],
                category=r["category"]
            )
            for r in roles
        ]


@router.post("/custom-roles")
async def create_custom_role(role: CustomRoleCreate, user: Dict[str, Any] = Depends(get_current_user)) -> RoleInfo:
    """Create a new custom role"""
    pool = await get_pool()

    # Check if role_key conflicts with built-in roles
    if role.role_key in AVAILABLE_ROLES:
        raise HTTPException(status_code=409, detail="Role key conflicts with built-in role")

    async with pool.acquire() as conn:
        # Check if custom role already exists
        existing = await conn.fetchrow("""
            SELECT id FROM custom_roles
            WHERE role_key = $1 AND tenant_id = 'a0000000-0000-0000-0000-000000000001'
        """, role.role_key)

        if existing:
            raise HTTPException(status_code=409, detail="Custom role already exists")

        await conn.execute("""
            INSERT INTO custom_roles (role_key, display_name, description, category, tenant_id, created_by)
            VALUES ($1, $2, $3, $4, 'a0000000-0000-0000-0000-000000000001', $5)
        """, role.role_key, role.display_name, role.description, role.category, user.get("user_id"))

        return RoleInfo(
            role=role.role_key,
            display_name=role.display_name,
            description=role.description,
            category=role.category
        )


@router.put("/custom-roles/{role_key}")
async def update_custom_role(role_key: str, updates: CustomRoleUpdate, user: Dict[str, Any] = Depends(get_current_user)) -> RoleInfo:
    """Update a custom role"""

    if role_key in AVAILABLE_ROLES:
        raise HTTPException(status_code=400, detail="Cannot modify built-in roles")

    pool = await get_pool()

    async with pool.acquire() as conn:
        current = await conn.fetchrow("""
            SELECT role_key, display_name, description, category
            FROM custom_roles
            WHERE role_key = $1 AND tenant_id = 'a0000000-0000-0000-0000-000000000001'
              AND is_active = true
        """, role_key)

        if not current:
            raise HTTPException(status_code=404, detail="Custom role not found")

        new_display = updates.display_name or current["display_name"]
        new_desc = updates.description or current["description"]
        new_cat = updates.category or current["category"]

        await conn.execute("""
            UPDATE custom_roles
            SET display_name = $1, description = $2, category = $3
            WHERE role_key = $4 AND tenant_id = 'a0000000-0000-0000-0000-000000000001'
        """, new_display, new_desc, new_cat, role_key)

        return RoleInfo(
            role=role_key,
            display_name=new_display,
            description=new_desc,
            category=new_cat
        )


@router.delete("/custom-roles/{role_key}")
async def delete_custom_role(role_key: str, user: Dict[str, Any] = Depends(get_current_user)):
    """Delete a custom role (soft delete)"""

    if role_key in AVAILABLE_ROLES:
        raise HTTPException(status_code=400, detail="Cannot delete built-in roles")

    pool = await get_pool()

    async with pool.acquire() as conn:
        # Soft delete the role
        result = await conn.execute("""
            UPDATE custom_roles SET is_active = false
            WHERE role_key = $1 AND tenant_id = 'a0000000-0000-0000-0000-000000000001'
        """, role_key)

        if result == "UPDATE 0":
            raise HTTPException(status_code=404, detail="Custom role not found")

        # Also revoke all assignments with this role
        await conn.execute("""
            UPDATE role_assignments SET revoked_at = NOW()
            WHERE role = $1 AND tenant_id = 'a0000000-0000-0000-0000-000000000001'
              AND revoked_at IS NULL
        """, role_key)

        return {"status": "deleted", "role_key": role_key}
