"""Role repository — all DB queries for roles and their permission matrices."""

from datetime import datetime, timezone

from sqlalchemy import delete, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.models.role import AksiEnum, ModulEnum, Role, RolePermission


async def get_all(
    db: AsyncSession, spbu_id: int | None = None
) -> list[Role]:
    """Return all roles, optionally scoped to a specific SPBU (includes global/seed roles)."""
    query = (
        select(Role)
        .where(Role.deleted_at.is_(None))
        .options(selectinload(Role.permissions))
    )
    if spbu_id is not None:
        query = query.where(
            or_(Role.spbu_id.is_(None), Role.spbu_id == spbu_id)
        )
    result = await db.execute(query.order_by(Role.is_system.desc(), Role.nama))
    return list(result.scalars().all())


async def get_by_id(db: AsyncSession, role_id: int) -> Role | None:
    """Fetch a single role by primary key, with its permissions loaded."""
    result = await db.execute(
        select(Role)
        .where(Role.id == role_id, Role.deleted_at.is_(None))
        .options(selectinload(Role.permissions))
    )
    return result.scalar_one_or_none()


async def create(db: AsyncSession, **kwargs) -> Role:
    """Create a new role record. Flush only — caller commits."""
    role = Role(**kwargs)
    db.add(role)
    await db.flush()
    return await get_by_id(db, role.id)  # reload with permissions


async def update(db: AsyncSession, role: Role, **kwargs) -> Role:
    """Update scalar fields on an existing role. Flush only — caller commits."""
    for key, value in kwargs.items():
        setattr(role, key, value)
    await db.flush()
    return await get_by_id(db, role.id)  # reload with permissions


async def delete_role(db: AsyncSession, role: Role, hard_delete: bool = True) -> None:
    """Delete a role record. Flush only — caller commits."""
    if hard_delete:
        await db.delete(role)
    else:
        role.deleted_at = datetime.now(timezone.utc)
    await db.flush()


async def get_permissions(db: AsyncSession, role_id: int) -> list[RolePermission]:
    """Return all permission entries for a given role."""
    result = await db.execute(
        select(RolePermission).where(RolePermission.role_id == role_id)
    )
    return list(result.scalars().all())


async def set_permissions(
    db: AsyncSession,
    role_id: int,
    permissions: list[tuple[ModulEnum, AksiEnum]],
) -> list[RolePermission]:
    """Atomically replace the full permission matrix for a role (delete-all then insert-all). Flush only — caller commits."""
    await db.execute(
        delete(RolePermission).where(RolePermission.role_id == role_id)
    )
    new_perms = [
        RolePermission(role_id=role_id, modul=modul, aksi=aksi)
        for modul, aksi in permissions
    ]
    db.add_all(new_perms)
    await db.flush()
    return new_perms


async def has_permission(
    db: AsyncSession, role_id: int, modul: ModulEnum, aksi: AksiEnum
) -> bool:
    """Return True if the role has the specified module+action permission."""
    result = await db.execute(
        select(RolePermission).where(
            RolePermission.role_id == role_id,
            RolePermission.modul == modul,
            RolePermission.aksi == aksi,
        )
    )
    return result.scalar_one_or_none() is not None
