"""Out of office: while someone is away, a colleague sees and acts on the
workflow steps waiting on them (app/crud/workflow_engine.py checks these
when listing and acting on approvals). Anyone sets their own; setting one
for someone else needs Workflows edit."""

import uuid
from datetime import datetime, timezone

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

from app.core.database import get_db
from app.core.permissions import deny_vendor, resolve_action_matrix
from app.core.security import AuthContext, get_auth_context
from app.crud.audit import write_audit_log
from app.models.identity import User
from app.models.workflow import Workflow, WorkflowDelegation
from app.schemas.workflow import WorkflowDelegationCreate, WorkflowDelegationRead

router = APIRouter(prefix="/workflow-delegations", tags=["workflow-delegations"], dependencies=[Depends(deny_vendor())])


async def _can_manage(db: AsyncSession, ctx: AuthContext) -> bool:
    matrix, admin = await resolve_action_matrix(db, ctx.tenant_id, ctx.user_id)
    return admin or "edit" in matrix.get("workflows", [])


async def _read(db: AsyncSession, tenant_id: uuid.UUID, rows: list[WorkflowDelegation]) -> list[WorkflowDelegationRead]:
    people = {r.user_id for r in rows} | {r.delegate_id for r in rows}
    names = dict((await db.execute(select(User.id, User.name).where(User.id.in_(people)))).all()) if people else {}
    wf_ids = {r.workflow_id for r in rows if r.workflow_id}
    wf_names = (
        dict((await db.execute(select(Workflow.workflow_id, Workflow.name).where(Workflow.tenant_id == tenant_id, Workflow.workflow_id.in_(wf_ids), Workflow.is_current.is_(True)))).all())
        if wf_ids
        else {}
    )
    return [
        WorkflowDelegationRead.model_validate(r).model_copy(
            update={"user_name": names.get(r.user_id), "delegate_name": names.get(r.delegate_id), "workflow_name": wf_names.get(r.workflow_id) if r.workflow_id else None}
        )
        for r in rows
    ]


@router.get("", response_model=list[WorkflowDelegationRead])
async def list_delegations(
    all_: bool = Query(False, alias="all"),
    include_past: bool = False,
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
) -> list[WorkflowDelegationRead]:
    """Yours — given and received — or, with ?all=true, everyone's (Workflows edit)."""
    stmt = select(WorkflowDelegation).where(WorkflowDelegation.tenant_id == ctx.tenant_id)
    if all_:
        if not await _can_manage(db, ctx):
            raise HTTPException(status_code=403, detail="You need Workflows edit to see everyone's.")
    else:
        stmt = stmt.where(or_(WorkflowDelegation.user_id == ctx.user_id, WorkflowDelegation.delegate_id == ctx.user_id))
    if not include_past:
        stmt = stmt.where(WorkflowDelegation.ends_at > datetime.now(timezone.utc))
    rows = list((await db.execute(stmt.order_by(WorkflowDelegation.starts_at))).scalars().all())
    return await _read(db, ctx.tenant_id, rows)


@router.post("", response_model=WorkflowDelegationRead, status_code=status.HTTP_201_CREATED)
async def create_delegation(payload: WorkflowDelegationCreate, ctx: AuthContext = Depends(get_auth_context), db: AsyncSession = Depends(get_db)) -> WorkflowDelegationRead:
    user_id = payload.user_id or ctx.user_id
    if user_id != ctx.user_id and not await _can_manage(db, ctx):
        raise HTTPException(status_code=403, detail="You can only set your own out-of-office.")
    if payload.ends_at <= payload.starts_at:
        raise HTTPException(status_code=422, detail="The end must be after the start.")
    if payload.delegate_id == user_id:
        raise HTTPException(status_code=422, detail="Pick someone else to stand in.")
    found = (await db.execute(select(User.id).where(User.tenant_id == ctx.tenant_id, User.id.in_([user_id, payload.delegate_id]), User.status == "active"))).scalars().all()
    if len(set(found)) != 2:
        raise HTTPException(status_code=422, detail="Both people must be active users of this workspace.")
    if payload.workflow_id is not None:
        exists = (await db.execute(select(Workflow.workflow_id).where(Workflow.tenant_id == ctx.tenant_id, Workflow.workflow_id == payload.workflow_id).limit(1))).first()
        if exists is None:
            raise HTTPException(status_code=422, detail="That workflow doesn't exist.")
    # Two stand-ins for the same person, workflow and time would be ambiguous.
    clash = (
        await db.execute(
            select(WorkflowDelegation.id).where(
                WorkflowDelegation.tenant_id == ctx.tenant_id,
                WorkflowDelegation.user_id == user_id,
                WorkflowDelegation.starts_at < payload.ends_at,
                WorkflowDelegation.ends_at > payload.starts_at,
                or_(WorkflowDelegation.workflow_id.is_(None), payload.workflow_id is None, WorkflowDelegation.workflow_id == payload.workflow_id),
            )
        )
    ).first()
    if clash is not None:
        raise HTTPException(status_code=409, detail="There's already a stand-in for that time — change or remove it first.")
    row = WorkflowDelegation(
        tenant_id=ctx.tenant_id,
        user_id=user_id,
        delegate_id=payload.delegate_id,
        starts_at=payload.starts_at,
        ends_at=payload.ends_at,
        workflow_id=payload.workflow_id,
        reason=(payload.reason or "").strip() or None,
        created_by=ctx.user_id,
    )
    db.add(row)
    await db.flush()
    await write_audit_log(db, ctx.tenant_id, ctx.user_id, "workflow_delegation.created", "workflow_delegation", row.id, {"user_id": str(user_id), "delegate_id": str(payload.delegate_id)})
    await db.refresh(row)
    return (await _read(db, ctx.tenant_id, [row]))[0]


@router.delete("/{delegation_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_delegation(delegation_id: uuid.UUID, ctx: AuthContext = Depends(get_auth_context), db: AsyncSession = Depends(get_db)) -> None:
    row = (await db.execute(select(WorkflowDelegation).where(WorkflowDelegation.tenant_id == ctx.tenant_id, WorkflowDelegation.id == delegation_id))).scalar_one_or_none()
    if row is None:
        raise HTTPException(status_code=404, detail="Not found")
    if row.user_id != ctx.user_id and not await _can_manage(db, ctx):
        raise HTTPException(status_code=403, detail="You can only remove your own out-of-office.")
    await db.delete(row)
    await write_audit_log(db, ctx.tenant_id, ctx.user_id, "workflow_delegation.deleted", "workflow_delegation", delegation_id, {})
