"""
test_agent.py — `pytest -q` (offline). Encodes the lab's success criteria:
routing, tool-not-LLM money facts, human-in-the-loop, least-privilege, durable
resume, and audit.
"""
from __future__ import annotations

import json

import pytest

from agent import BankingAgent
from agentgraph import Interrupt
from bank import CoreBanking

AMAL = "user_amal"
ACCT = "AE070331234567890123456"
OMAR_ACCT = "AE980332222333344445555"


def test_policy_path_is_grounded():
    a = BankingAgent()
    r = a.handle("How long do I have to dispute a transaction?", AMAL, ACCT, "p1")
    assert r["intent"] == "policy"
    assert "60 days" in r["answer"] and "[" in r["answer"]  # cited


def test_balance_comes_from_tool():
    a = BankingAgent()
    r = a.handle("what is my balance", AMAL, ACCT, "b1")
    # The number must equal the system-of-record value, not anything the LLM made up.
    assert r["tool_result"]["balance"] == 1250.00
    assert "1250" in r["answer"]


def test_transfer_pauses_for_approval_then_executes():
    a = BankingAgent()
    with pytest.raises(Interrupt):
        a.handle("transfer 100 to AE_friend", AMAL, ACCT, "t1")
    r = a.approve("t1", approved=True, approver="ops")
    assert r["tool_result"]["status"] == "executed"
    assert r["tool_result"]["new_balance"] == 1150.00  # 1250 - 100


def test_transfer_declined_does_not_move_money():
    core = CoreBanking()
    a = BankingAgent(core)
    with pytest.raises(Interrupt):
        a.handle("transfer 100 to AE_friend", AMAL, ACCT, "t2")
    r = a.approve("t2", approved=False, approver="ops")
    assert "cancelled" in r["answer"].lower()
    assert core.accounts[ACCT].balance == 1250.00       # unchanged


def test_least_privilege_blocks_unowned_account():
    a = BankingAgent()
    # Amal tries to transfer from Omar's account; even if "approved", the tool blocks it.
    with pytest.raises(Interrupt):
        a.handle("transfer 10 to AE_y", AMAL, OMAR_ACCT, "t3")
    r = a.approve("t3", approved=True, approver="ops")
    assert "failed" in r["answer"].lower() or "can't" in r["answer"].lower()


def test_refusal_for_out_of_scope():
    a = BankingAgent()
    r = a.handle("Should I invest in stocks?", AMAL, ACCT, "r1")
    assert r.get("refused") is True


def test_durability_resume_after_serialize():
    a = BankingAgent()
    with pytest.raises(Interrupt):
        a.handle("transfer 100 to AE_z", AMAL, ACCT, "d1")
    # Simulate a crash: the checkpoint is serializable to JSON and survives.
    blob = a.ckpt.serialize("d1")
    assert json.loads(blob)[-1]["status"] == "interrupted"
    # Resume continues from the saved state.
    r = a.approve("d1", approved=True, approver="ops")
    assert r["tool_result"]["status"] == "executed"


def test_audit_trail_records_every_step():
    a = BankingAgent()
    with pytest.raises(Interrupt):
        a.handle("transfer 100 to AE_w", AMAL, ACCT, "a1")
    r = a.approve("a1", approved=True, approver="ops_layla")
    steps = [e["step"] for e in r["audit"]]
    assert "route" in steps and "prepare_transfer" in steps
    assert "human_review" in steps and "tool:initiate_transfer" in steps
    # approver is recorded for compliance
    assert any(e.get("approver") == "ops_layla" for e in r["audit"])
