"""
Row-level scoped record visibility — lets an admin restrict which Master
Records a user can see/edit within a Master, without touching Postgres RLS.
Reads apply it as a SQL condition on the scope field
(app/crud/record_sql.py::scope_clause); is_restricted_for below decides
whether, and to which ids, a caller is restricted.

A Master opts in via `Master.scope_field`, naming one of its own `reference`
-type fields (e.g. "office") as the row's owning scope. A user is granted
visibility via UserScopeAssignment rows: `(user_id, master_id, record_id)`,
where `master_id` is the *scope-defining* Master the reference field points
at (e.g. "Offices") and `record_id` one of its records. A user with zero
assignments against that scope-defining Master is unrestricted for anything
scoped by it — see the module docstring in the plan for the reasoning.
`is_system_role` always bypasses this entirely, same as it already bypasses
Role.action_matrix checks (app/core/permissions.py::resolve_action_matrix).
"""

import uuid

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.permissions import resolve_action_matrix
from app.models.master import Master, UserScopeAssignment


async def resolve_user_scope(db: AsyncSession, tenant_id: uuid.UUID, user_id: uuid.UUID | None) -> dict[uuid.UUID, set[str]]:
    """{scope-defining master_id: {assigned record_id strings}} for this
    user. An empty dict (no assignments anywhere) means fully unrestricted."""
    if user_id is None:
        return {}
    rows = (
        await db.execute(
            select(UserScopeAssignment).where(UserScopeAssignment.tenant_id == tenant_id, UserScopeAssignment.user_id == user_id)
        )
    ).scalars().all()
    scope: dict[uuid.UUID, set[str]] = {}
    for row in rows:
        scope.setdefault(row.master_id, set()).add(str(row.record_id))
    return scope


def _scope_field_reference_master(master: Master) -> uuid.UUID | None:
    """The scope-defining Master's id, resolved from master.scope_field's
    entry in master.field_schema — None if the Master isn't scoped."""
    if not master.scope_field:
        return None
    field = next((f for f in master.field_schema if f.get("name") == master.scope_field), None)
    if field is None or not field.get("reference_master_id"):
        return None
    return uuid.UUID(str(field["reference_master_id"]))


def _record_scope_values(field_values: dict, scope_field: str) -> set[str]:
    """The raw reference value(s) a record's scope field holds, normalized
    to a set of id strings — a `multi: true` reference field stores a list."""
    raw = field_values.get(scope_field)
    if raw is None:
        return set()
    if isinstance(raw, list):
        return {str(v) for v in raw if v is not None}
    return {str(raw)}


async def is_restricted_for(db: AsyncSession, tenant_id: uuid.UUID, user_id: uuid.UUID | None, master: Master) -> set[str] | None:
    """None means unrestricted (system admin, unscoped Master, or a user
    with no assignments against this Master's scope-defining Master).
    Otherwise, the set of scope-field values the caller is allowed to see."""
    if not master.scope_field:
        return None
    _, is_system_admin = await resolve_action_matrix(db, tenant_id, user_id)
    if is_system_admin:
        return None
    reference_master_id = _scope_field_reference_master(master)
    if reference_master_id is None:
        return None
    user_scope = await resolve_user_scope(db, tenant_id, user_id)
    allowed = user_scope.get(reference_master_id)
    if not allowed:
        return None
    return allowed


async def check_value_in_scope(
    db: AsyncSession, tenant_id: uuid.UUID, user_id: uuid.UUID | None, master: Master, field_values: dict
) -> bool:
    """For create/update: does this (about-to-be-written) field_values dict
    fall within the caller's scope? True if unrestricted."""
    allowed = await is_restricted_for(db, tenant_id, user_id, master)
    if allowed is None:
        return True
    return bool(_record_scope_values(field_values, master.scope_field) & allowed)
