"""
Role management API routes.
"""
from typing import List, Dict
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select, func, delete
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.deps import get_db, get_current_user
from app.core.response import success_response
from app.models.user import Role, Permission, RolePermission, UserRole
from app.schemas.user import RoleCreate, RoleUpdate

router = APIRouter()


def _permission_to_dict(permission: Permission) -> Dict:
    return {
        "id": permission.id,
        "permission_name": permission.permission_name,
        "permission_code": permission.permission_code,
        "module": permission.module,
        "module_name": permission.module,
        "resource": permission.resource,
        "action": permission.action,
        "description": permission.description,
        "created_at": permission.created_at,
    }


def _role_to_dict(role: Role, permissions: List[Permission], user_count: int) -> Dict:
    return {
        "id": role.id,
        "role_name": role.role_name,
        "role_code": role.role_code,
        "description": role.description,
        "status": role.status,
        "sort_order": role.sort_order,
        "created_at": role.created_at,
        "permissions": [_permission_to_dict(p) for p in permissions],
        "user_count": user_count,
    }


async def _ensure_permissions_exist(db: AsyncSession, permission_ids: List[int]) -> None:
    if not permission_ids:
        return
    result = await db.execute(select(Permission.id).where(Permission.id.in_(permission_ids)))
    existing_ids = {row[0] for row in result.all()}
    missing = set(permission_ids) - existing_ids
    if missing:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Invalid permission IDs: {sorted(missing)}",
        )


async def _set_role_permissions(
    db: AsyncSession,
    role_id: int,
    permission_ids: List[int],
    operator_id: int,
) -> None:
    await db.execute(delete(RolePermission).where(RolePermission.role_id == role_id))
    if permission_ids:
        for permission_id in permission_ids:
            db.add(
                RolePermission(
                    role_id=role_id,
                    permission_id=permission_id,
                    created_by=operator_id,
                )
            )


@router.get("/")
async def list_roles(
    db: AsyncSession = Depends(get_db),
    current_user=Depends(get_current_user),
):
    result = await db.execute(
        select(Role).where(Role.deleted_at.is_(None)).order_by(Role.sort_order, Role.id)
    )
    roles = list(result.scalars().all())

    role_ids = [role.id for role in roles]
    permission_map: Dict[int, List[Permission]] = {role.id: [] for role in roles}
    user_count_map: Dict[int, int] = {role.id: 0 for role in roles}

    if role_ids:
        perm_result = await db.execute(
            select(RolePermission.role_id, Permission)
            .join(Permission, Permission.id == RolePermission.permission_id)
            .where(RolePermission.role_id.in_(role_ids))
        )
        for role_id, permission in perm_result.all():
            permission_map.setdefault(role_id, []).append(permission)

        count_result = await db.execute(
            select(UserRole.role_id, func.count(UserRole.user_id))
            .where(UserRole.role_id.in_(role_ids))
            .group_by(UserRole.role_id)
        )
        for role_id, count in count_result.all():
            user_count_map[role_id] = count

    items = [
        _role_to_dict(role, permission_map.get(role.id, []), user_count_map.get(role.id, 0))
        for role in roles
    ]
    return success_response(data=items)


@router.post("/", status_code=status.HTTP_201_CREATED)
async def create_role(
    payload: RoleCreate,
    db: AsyncSession = Depends(get_db),
    current_user=Depends(get_current_user),
):
    existing = await db.execute(select(Role).where(Role.role_code == payload.role_code))
    if existing.scalar_one_or_none():
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Role code already exists")

    existing_name = await db.execute(select(Role).where(Role.role_name == payload.role_name))
    if existing_name.scalar_one_or_none():
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Role name already exists")

    await _ensure_permissions_exist(db, payload.permission_ids)

    role = Role(
        role_code=payload.role_code,
        role_name=payload.role_name,
        description=payload.description,
        status="active",
        created_by=current_user.id,
    )
    db.add(role)
    await db.flush()

    await _set_role_permissions(db, role.id, payload.permission_ids, current_user.id)
    await db.commit()
    await db.refresh(role)

    perm_result = await db.execute(
        select(Permission)
        .join(RolePermission, RolePermission.permission_id == Permission.id)
        .where(RolePermission.role_id == role.id)
    )
    permissions = list(perm_result.scalars().all())

    return success_response(data=_role_to_dict(role, permissions, 0))


@router.put("/{role_id}")
async def update_role(
    role_id: int,
    payload: RoleUpdate,
    db: AsyncSession = Depends(get_db),
    current_user=Depends(get_current_user),
):
    result = await db.execute(select(Role).where(Role.id == role_id).where(Role.deleted_at.is_(None)))
    role = result.scalar_one_or_none()
    if not role:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Role not found")

    update_data = payload.model_dump(exclude_unset=True)

    if "role_name" in update_data:
        role_name = update_data.pop("role_name")
        existing = await db.execute(
            select(Role).where(Role.role_name == role_name).where(Role.id != role_id)
        )
        if existing.scalar_one_or_none():
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Role name already exists")
        role.role_name = role_name

    if "description" in update_data:
        role.description = update_data.pop("description")

    permission_ids = update_data.pop("permission_ids", None)
    if permission_ids is not None:
        await _ensure_permissions_exist(db, permission_ids)
        await _set_role_permissions(db, role.id, permission_ids, current_user.id)

    role.updated_by = current_user.id
    db.add(role)
    await db.commit()
    await db.refresh(role)

    perm_result = await db.execute(
        select(Permission)
        .join(RolePermission, RolePermission.permission_id == Permission.id)
        .where(RolePermission.role_id == role.id)
    )
    permissions = list(perm_result.scalars().all())

    count_result = await db.execute(
        select(func.count(UserRole.user_id)).where(UserRole.role_id == role.id)
    )
    user_count = count_result.scalar() or 0

    return success_response(data=_role_to_dict(role, permissions, user_count))


@router.delete("/{role_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_role(
    role_id: int,
    db: AsyncSession = Depends(get_db),
    current_user=Depends(get_current_user),
):
    result = await db.execute(select(Role).where(Role.id == role_id).where(Role.deleted_at.is_(None)))
    role = result.scalar_one_or_none()
    if not role:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Role not found")

    role.deleted_at = func.now()
    role.deleted_by = current_user.id
    role.status = "disabled"

    await db.commit()
    return success_response(message="Deleted")
