"""Approvals: the workflow steps waiting on the signed-in person, and acting
on them (app/routers/approvals.py)."""

import uuid

from app.core.auth import create_access_token, hash_password
from app.models.identity import Role, User, UserRole
from tests.conftest import AsyncSessionLocal


async def _person(tenant_id, role_id=None):
    async with AsyncSessionLocal() as db:
        async with db.begin():
            user = User(tenant_id=tenant_id, name=f"P {uuid.uuid4().hex[:4]}", email=f"p-{uuid.uuid4().hex[:8]}@example.com", password_hash=hash_password("pytest-password-123"), status="active")
            db.add(user)
            await db.flush()
            if role_id:
                db.add(UserRole(user_id=user.id, role_id=role_id))
            user_id = user.id
    return {"Authorization": f"Bearer {create_access_token(tenant_id=tenant_id, user_id=user_id, vendor_id=None)}"}, user_id


async def test_approvals_wait_on_their_approvers(client, auth_headers, test_tenant):
    tenant_id = test_tenant["tenant_id"]
    async with AsyncSessionLocal() as db:
        async with db.begin():
            role = Role(tenant_id=tenant_id, name=f"Reviewers {uuid.uuid4().hex[:4]}", action_matrix={})
            db.add(role)
            await db.flush()
            role_id = role.id
    reviewer, _ = await _person(tenant_id, role_id)
    outsider, _ = await _person(tenant_id)
    steps = [
        {"key": "start", "type": "start", "label": "Start", "next_steps": [{"to": "review"}]},
        {"key": "review", "type": "approval", "label": "Review", "approver_roles": [str(role_id)], "next_steps": [{"to": "approved"}, {"to": "rejected"}, {"to": "escalate", "condition": {"driver_field": "amount", "operator": "gt", "value": 1000}}]},
        {"key": "escalate", "type": "approval", "label": "Escalate", "approver_roles": [str(role_id)], "next_steps": [{"to": "approved"}]},
        {"key": "approved", "type": "end", "label": "Approved"},
        {"key": "rejected", "type": "end", "label": "Rejected"},
    ]
    wf = (await client.post("/workflows", headers=auth_headers, json={"name": "Leave request", "step_definitions": steps})).json()
    assert (await client.post(f"/workflows/{wf['workflow_id']}/publish", headers=auth_headers)).status_code == 200
    started = await client.post("/workflow-instances", headers=auth_headers, json={"workflow_id": wf["workflow_id"], "initial_step": "start", "context": {"amount": 200}})
    assert started.status_code == 201, started.text
    instance_id = started.json()["id"]
    # The Start step moves straight on to the first step that waits.
    assert started.json()["current_step"] == "review"

    # The reviewer's role is the approver; someone else sees nothing.
    tasks = (await client.get("/approvals", headers=reviewer)).json()
    task = next(t for t in tasks if t["instance_id"] == instance_id)
    assert task["workflow_name"] == "Leave request" and task["step_label"] == "Review" and task["context"] == {"amount": 200}
    # The conditional route doesn't apply to an amount of 200.
    assert [(c["to"], c["complete"]) for c in task["choices"]] == [("approved", True), ("rejected", True)]
    assert all(t["instance_id"] != instance_id for t in (await client.get("/approvals", headers=outsider)).json())
    assert (await client.post(f"/approvals/{instance_id}/act", headers=outsider, json={"to": "approved"})).status_code == 404

    # Only a next step of this one; then it's done and off the list.
    assert (await client.post(f"/approvals/{instance_id}/act", headers=reviewer, json={"to": "escalate"})).status_code == 422
    done = await client.post(f"/approvals/{instance_id}/act", headers=reviewer, json={"to": "approved", "comment": "Enjoy"})
    assert done.status_code == 200, done.text
    assert done.json()["status"] == "completed" and done.json()["current_step"] == "approved"
    history = (await client.get(f"/workflow-instances/{instance_id}/history", headers=auth_headers)).json()
    assert [h["action"] for h in history] == ["started", "submitted", "approved", "completed"]
    assert history[2]["comment"] == "Enjoy" and history[2]["actor_name"]
    assert all(t["instance_id"] != instance_id for t in (await client.get("/approvals", headers=reviewer)).json())
