"""Record access policies of a Master (datasource) or Form — see
app/crud/access_policy.py for what they do. Reading or changing one needs
the module's edit permission; every change is audit-logged."""

import uuid

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.database import get_db
from app.core.permissions import deny_vendor, require_permission
from app.core.security import AuthContext, get_auth_context
from app.crud.access_policy import load_policy
from app.crud.audit import write_audit_log
from app.crud.change_log import check_fresh
from app.models.access import RecordAccessPolicy
from app.models.identity import Role
from app.models.master import Master
from app.models.workflow import Form
from app.schemas.access import RecordAccessPolicyIn, RecordAccessPolicyRead

router = APIRouter(tags=["access"])


async def _require_source(db: AsyncSession, tenant_id: uuid.UUID, source_type: str, source_id: uuid.UUID) -> None:
    if source_type == "master":
        found = await db.execute(select(Master.id).where(Master.tenant_id == tenant_id, Master.id == source_id))
    else:
        found = await db.execute(select(Form.form_id).where(Form.tenant_id == tenant_id, Form.form_id == source_id).limit(1))
    if found.scalar_one_or_none() is None:
        raise HTTPException(status_code=404, detail=f"{'Datasource' if source_type == 'master' else 'Form'} not found")


async def _read(db: AsyncSession, tenant_id: uuid.UUID, source_type: str, source_id: uuid.UUID) -> RecordAccessPolicyRead:
    await _require_source(db, tenant_id, source_type, source_id)
    policy = await load_policy(db, tenant_id, source_type, source_id)
    if policy is None:
        return RecordAccessPolicyRead(source_type=source_type, source_id=source_id, enabled=False, rules=[])
    return RecordAccessPolicyRead.model_validate(policy)


async def _write(
    db: AsyncSession, ctx: AuthContext, source_type: str, source_id: uuid.UUID, payload: RecordAccessPolicyIn
) -> RecordAccessPolicyRead:
    await _require_source(db, ctx.tenant_id, source_type, source_id)
    role_ids = {r for rule in payload.rules for r in rule.role_ids}
    if role_ids:
        known = set((await db.execute(select(Role.id).where(Role.tenant_id == ctx.tenant_id, Role.id.in_(role_ids)))).scalars())
        unknown = role_ids - known
        if unknown:
            raise HTTPException(status_code=422, detail=f"Unknown role(s): {', '.join(str(r) for r in unknown)}")
    policy = await load_policy(db, ctx.tenant_id, source_type, source_id)
    before = None
    if policy is None:
        policy = RecordAccessPolicy(tenant_id=ctx.tenant_id, source_type=source_type, source_id=source_id)
        db.add(policy)
    else:
        check_fresh(policy, payload.expected_updated_at)
        before = {"enabled": policy.enabled, "rules": policy.rules}
    policy.enabled = payload.enabled
    policy.rules = [r.model_dump(mode="json") for r in payload.rules]
    policy.updated_by = ctx.user_id
    await db.flush()
    await db.refresh(policy)
    await write_audit_log(
        db, ctx.tenant_id, ctx.user_id, "access_policy.update", source_type, source_id,
        {"before": before, "after": {"enabled": policy.enabled, "rules": policy.rules}},
    )
    return RecordAccessPolicyRead.model_validate(policy)


@router.get("/masters/{master_id}/access-policy", response_model=RecordAccessPolicyRead)
async def get_master_access_policy(
    master_id: uuid.UUID,
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
    _: AuthContext = Depends(require_permission("masters", "edit")),
    __: AuthContext = Depends(deny_vendor()),
) -> RecordAccessPolicyRead:
    return await _read(db, ctx.tenant_id, "master", master_id)


@router.put("/masters/{master_id}/access-policy", response_model=RecordAccessPolicyRead)
async def set_master_access_policy(
    master_id: uuid.UUID,
    payload: RecordAccessPolicyIn,
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
    _: AuthContext = Depends(require_permission("masters", "edit")),
    __: AuthContext = Depends(deny_vendor()),
) -> RecordAccessPolicyRead:
    return await _write(db, ctx, "master", master_id, payload)


@router.get("/forms/{form_id}/access-policy", response_model=RecordAccessPolicyRead)
async def get_form_access_policy(
    form_id: uuid.UUID,
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
    _: AuthContext = Depends(require_permission("forms", "edit")),
    __: AuthContext = Depends(deny_vendor()),
) -> RecordAccessPolicyRead:
    return await _read(db, ctx.tenant_id, "form", form_id)


@router.put("/forms/{form_id}/access-policy", response_model=RecordAccessPolicyRead)
async def set_form_access_policy(
    form_id: uuid.UUID,
    payload: RecordAccessPolicyIn,
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
    _: AuthContext = Depends(require_permission("forms", "edit")),
    __: AuthContext = Depends(deny_vendor()),
) -> RecordAccessPolicyRead:
    return await _write(db, ctx, "form", form_id, payload)
