"""Field-level permissions on Forms — each field's access_rules
(app/schemas/workflow.py::FormFieldAccessRule) make it read-only or hidden
for some roles, optionally only while the submission's workflow is at given
steps.

Enforced on the server:
  * reads (a submission, submission lists, dataset queries over the form)
    leave hidden fields out, and filtering, sorting or grouping by one is
    refused — otherwise the value could be probed;
  * edits keep the stored value of read-only and hidden fields, whatever
    was sent (app/crud/form_integrity.py's `locked`);
  * a hidden field's required check is skipped — the person can't fill it.

Which step applies: a submission linked to a workflow instance is at that
instance's current step. Lists and dataset queries span many submissions,
so only rules without steps apply there; step rules govern the single
submission being viewed or edited (the workflow task screen).

On a new submission, values for read-only fields are accepted — they are
how prefills and defaults arrive — so read-only protects stored data, not
what a creator first enters.
"""

import uuid

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

from app.crud.access_policy import user_roles
from app.models.workflow import FormSubmission, WorkflowInstance

_RANK = {"read_only": 1, "hidden": 2}


def effective_access(field_defs: list[dict], role_ids: set[str], step: str | None, with_step_rules: bool = True) -> dict[str, str]:
    """{field name: "read_only" | "hidden"} — fields absent are editable."""
    out: dict[str, str] = {}
    for f in field_defs:
        for rule in f.get("access_rules") or []:
            roles = {str(r) for r in rule.get("role_ids") or []}
            steps = rule.get("steps") or []
            if roles and not roles & role_ids:
                continue
            if steps and (not with_step_rules or step not in steps):
                continue
            access = rule.get("access") or "read_only"
            if _RANK.get(access, 0) > _RANK.get(out.get(f["name"], ""), 0):
                out[f["name"]] = access
    return out


async def submission_step(db: AsyncSession, tenant_id: uuid.UUID, submission: FormSubmission | None) -> str | None:
    if submission is None or submission.workflow_instance_id is None:
        return None
    return (
        await db.execute(
            select(WorkflowInstance.current_step).where(
                WorkflowInstance.tenant_id == tenant_id, WorkflowInstance.id == submission.workflow_instance_id
            )
        )
    ).scalar_one_or_none()


async def access_for(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    user_id: uuid.UUID | None,
    field_defs: list[dict],
    step: str | None = None,
    with_step_rules: bool = True,
) -> dict[str, str]:
    if not any(f.get("access_rules") for f in field_defs):
        return {}
    roles, _ = await user_roles(db, tenant_id, user_id)
    return effective_access(field_defs, roles, step, with_step_rules)


def hidden_names(access: dict[str, str]) -> set[str]:
    return {name for name, a in access.items() if a == "hidden"}


def without_hidden(values: dict, access: dict[str, str]) -> dict:
    hidden = hidden_names(access)
    return {k: v for k, v in values.items() if k not in hidden} if hidden else values
