from fastapi import APIRouter, Depends, HTTPException, status, Request, Response
from sqlalchemy.orm import Session
from sqlalchemy import text
from typing import List
import logging
import hashlib
import json
from ..database import get_db
from ..models.permission import Permission, Role, RolePermission
from ..models.user import User
from ..schemas.permission import (
    PermissionCreate, PermissionUpdate, PermissionResponse,
    RoleCreate, RoleUpdate, RoleResponse,
    RolePermissionCreate, RolePermissionBulkUpdate, RolePermissionsResponse
)
from ..auth import get_current_admin_user, require_permission
from ..sse import publish_event

router = APIRouter(prefix="/api", tags=["roles", "permissions"])


# ==================== PERMISSIONS ====================

@router.get("/permissions")
async def get_permissions(
    category: str = None,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_permission("admin:permissions")),
    request: Request = None
):
    """Get all permissions (admin only)."""
    query = db.query(Permission)
    if category:
        query = query.filter(Permission.category == category)
    permissions = query.order_by(Permission.category, Permission.name).all()

    # Convert to JSON for ETag calculation
    result = [PermissionResponse.from_orm(p) for p in permissions]
    result_json = json.dumps([r.model_dump(mode='json') for r in result])
    etag = hashlib.md5(result_json.encode()).hexdigest()

    if request and request.headers.get("if-none-match") == etag:
        return Response(status_code=304)

    return Response(
        content=result_json,
        media_type="application/json",
        headers={"etag": etag}
    )


@router.post("/permissions", response_model=PermissionResponse, status_code=status.HTTP_201_CREATED)
async def create_permission(
    permission: PermissionCreate,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_permission("admin:permissions"))
):
    """Create a new permission (admin only)."""
    # Check if permission already exists
    existing = db.query(Permission).filter(Permission.name == permission.name).first()
    if existing:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Permission '{permission.name}' already exists"
        )

    new_permission = Permission(**permission.model_dump())
    db.add(new_permission)
    db.commit()
    db.refresh(new_permission)
    
    # Publish SSE event
    publish_event("permission_created", {"permission_id": str(new_permission.id), "permission_name": permission.name})
    
    return new_permission


@router.get("/permissions/{permission_id}", response_model=PermissionResponse)
async def get_permission(
    permission_id: str,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_permission("admin:permissions"))
):
    """Get a single permission by ID (admin only)."""
    permission = db.query(Permission).filter(Permission.id == permission_id).first()
    if not permission:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Permission '{permission_id}' not found"
        )
    return permission


@router.put("/permissions/{permission_id}", response_model=PermissionResponse)
async def update_permission(
    permission_id: str,
    permission_update: PermissionUpdate,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_permission("admin:permissions"))
):
    """Update a permission (admin only)."""
    permission = db.query(Permission).filter(Permission.id == permission_id).first()
    if not permission:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Permission '{permission_id}' not found"
        )

    update_data = permission_update.model_dump(exclude_unset=True)
    for field, value in update_data.items():
        setattr(permission, field, value)

    db.commit()
    db.refresh(permission)
    
    # Publish SSE event
    publish_event("permission_updated", {"permission_id": str(permission.id), "permission_name": permission.name})
    
    return permission


@router.delete("/permissions/{permission_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_permission(
    permission_id: str,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_permission("admin:permissions"))
):
    """Delete a permission (admin only)."""
    permission = db.query(Permission).filter(Permission.id == permission_id).first()
    if not permission:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Permission '{permission_id}' not found"
        )

    db.delete(permission)
    db.commit()
    
    # Publish SSE event
    publish_event("permission_deleted", {"permission_id": permission_id, "id": permission_id})
    
    return None


# ==================== ROLES ====================

@router.get("/roles")
async def get_roles(
    db: Session = Depends(get_db),
    current_user: User = Depends(require_permission("admin:roles")),
    request: Request = None
):
    """Get all roles (admin only)."""
    roles = db.query(Role).order_by(Role.priority.desc(), Role.name).all()
    # Get default role from database
    from sqlalchemy import text
    default_role_id = db.execute(text("SELECT id FROM roles WHERE is_default = true LIMIT 1")).scalar()

    # Manually serialize to convert Boolean objects to bool
    result = []
    for role in roles:
        permissions = [rp.permission for rp in role.permissions]
        # Determine if this is the default role based on database value
        is_default = str(role.id) == str(default_role_id) if default_role_id else False
        result.append(RoleResponse(
            id=role.id,
            name=role.name,
            description=role.description,
            color=role.color,
            icon=role.icon,
            is_default=is_default,
            priority=role.priority,
            created_at=role.created_at,
            updated_at=role.updated_at,
            permissions=permissions
        ))

    # Calculate ETag from result
    result_dict = [r.model_dump() for r in result]
    result_json = json.dumps(result_dict, default=str)
    etag = hashlib.md5(result_json.encode()).hexdigest()

    if request and request.headers.get("if-none-match") == etag:
        return Response(status_code=304)

    return result


@router.post("/roles", response_model=RoleResponse, status_code=status.HTTP_201_CREATED)
async def create_role(
    role: RoleCreate,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_permission("admin:roles"))
):
    """Create a new role (admin only)."""
    # Check if role already exists
    existing = db.query(Role).filter(Role.name == role.name).first()
    if existing:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Role '{role.name}' already exists"
        )

    # Create role
    new_role = Role(
        name=role.name,
        description=role.description,
        is_default=role.is_default,
        priority=role.priority
    )
    db.add(new_role)
    db.commit()
    db.refresh(new_role)

    # Ensure only one default role exists
    if role.is_default:
        db.query(Role).filter(Role.id != new_role.id).update({Role.is_default: False})
        db.commit()
        db.refresh(new_role)

    # Assign permissions if provided
    if role.permission_ids:
        for permission_id in role.permission_ids:
            role_permission = RolePermission(role_id=new_role.id, permission_id=permission_id)
            db.add(role_permission)
        db.commit()
        db.refresh(new_role)

    # Publish SSE event
    publish_event("role_created", {"role_id": str(new_role.id)})

    return new_role


@router.get("/roles/{role_id}", response_model=RoleResponse)
async def get_role(
    role_id: str,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_permission("admin:roles"))
):
    """Get a single role by ID (admin only)."""
    role = db.query(Role).filter(Role.id == role_id).first()
    if not role:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Role '{role_id}' not found"
        )
    return role


@router.put("/roles/{role_id}", response_model=RoleResponse)
async def update_role(
    role_id: str,
    role_update: RoleUpdate,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_permission("admin:roles"))
):
    """Update a role (admin only)."""
    role = db.query(Role).filter(Role.id == role_id).first()
    if not role:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Role '{role_id}' not found"
        )

    update_data = role_update.model_dump(exclude_unset=True)

    # Check if is_default is being set to True before removing permission_ids
    is_setting_default = update_data.get("is_default") == True

    permission_ids = update_data.pop("permission_ids", None)

    # Update role fields
    for field, value in update_data.items():
        setattr(role, field, value)

    # Ensure only one default role exists
    if is_setting_default:
        # Use direct SQL to update is_default to avoid transaction issues
        db.execute(text("UPDATE roles SET is_default = TRUE WHERE id = :role_id"), {"role_id": role_id})
        
        # Set other roles to False using direct SQL
        db.execute(text("UPDATE roles SET is_default = FALSE WHERE id != :role_id"), {"role_id": role_id})

    # Update permissions if provided
    if permission_ids is not None:
        # Remove existing permissions
        db.query(RolePermission).filter(RolePermission.role_id == role_id).delete()
        # Add new permissions
        for permission_id in permission_ids:
            role_permission = RolePermission(role_id=role_id, permission_id=permission_id)
            db.add(role_permission)

    db.commit()
    db.refresh(role)
    
    # Publish SSE event
    # If is_default was changed, send a special event to reload all roles
    if is_setting_default:
        publish_event("role_default_changed", {"role_id": str(role.id)})
    else:
        publish_event("role_updated", {"role_id": str(role.id)})

    return role


@router.delete("/roles/{role_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_role(
    role_id: str,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_permission("admin:roles"))
):
    """Delete a role (admin only)."""
    role = db.query(Role).filter(Role.id == role_id).first()
    if not role:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Role '{role_id}' not found"
        )

    # Check if role is a default role
    if role.is_default == True:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot delete default system roles"
        )

    # Check if role has users
    if role.users:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Cannot delete role '{role.name}' because it has assigned users"
        )

    db.delete(role)
    db.commit()
    
    # Publish SSE event
    publish_event("role_deleted", {"role_id": role_id})
    
    return None


# ==================== ROLE PERMISSIONS ====================

@router.get("/roles/{role_id}/permissions", response_model=RolePermissionsResponse)
async def get_role_permissions(
    role_id: str,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_permission("admin:roles"))
):
    """Get all permissions for a specific role (admin only)."""
    role = db.query(Role).filter(Role.id == role_id).first()
    if not role:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Role '{role_id}' not found"
        )

    permissions = [rp.permission for rp in role.permissions]
    return RolePermissionsResponse(
        role_id=role.id,
        role_name=role.name,
        permissions=permissions
    )


@router.post("/roles/{role_id}/permissions", response_model=RolePermissionsResponse)
async def add_role_permission(
    role_id: str,
    role_permission: RolePermissionCreate,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_permission("admin:roles"))
):
    """Add a permission to a role (admin only)."""
    role = db.query(Role).filter(Role.id == role_id).first()
    if not role:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Role '{role_id}' not found"
        )

    permission = db.query(Permission).filter(Permission.id == role_permission.permission_id).first()
    if not permission:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Permission '{role_permission.permission_id}' not found"
        )

    # Check if permission already assigned
    existing = db.query(RolePermission).filter(
        RolePermission.role_id == role_id,
        RolePermission.permission_id == role_permission.permission_id
    ).first()
    if existing:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Permission already assigned to role"
        )

    # Add permission
    role_permission = RolePermission(role_id=role_id, permission_id=role_permission.permission_id)
    db.add(role_permission)
    db.commit()
    db.refresh(role)

    permissions = [rp.permission for rp in role.permissions]
    return RolePermissionsResponse(
        role_id=role.id,
        role_name=role.name,
        permissions=permissions
    )


@router.delete("/roles/{role_id}/permissions/{permission_id}", response_model=RolePermissionsResponse)
async def remove_role_permission(
    role_id: str,
    permission_id: str,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_permission("admin:roles"))
):
    """Remove a permission from a role (admin only)."""
    role = db.query(Role).filter(Role.id == role_id).first()
    if not role:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Role '{role_id}' not found"
        )

    role_permission = db.query(RolePermission).filter(
        RolePermission.role_id == role_id,
        RolePermission.permission_id == permission_id
    ).first()
    if not role_permission:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Permission not assigned to role"
        )

    db.delete(role_permission)
    db.commit()
    db.refresh(role)

    permissions = [rp.permission for rp in role.permissions]
    return RolePermissionsResponse(
        role_id=role.id,
        role_name=role.name,
        permissions=permissions
    )


@router.put("/roles/{role_id}/permissions/bulk", response_model=RolePermissionsResponse)
async def bulk_update_role_permissions(
    role_id: str,
    bulk_update: RolePermissionBulkUpdate,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_permission("admin:roles"))
):
    """Bulk update permissions for a role (admin only)."""
    role = db.query(Role).filter(Role.id == role_id).first()
    if not role:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Role '{role_id}' not found"
        )

    # Remove all existing permissions
    db.query(RolePermission).filter(RolePermission.role_id == role_id).delete()

    # Add new permissions
    for permission_id in bulk_update.permission_ids:
        role_permission = RolePermission(role_id=role_id, permission_id=permission_id)
        db.add(role_permission)

    db.commit()
    db.refresh(role)

    permissions = [rp.permission for rp in role.permissions]
    return RolePermissionsResponse(
        role_id=role.id,
        role_name=role.name,
        permissions=permissions
    )
