"""DecisionLayer API sandbox: every contract-case call, with fixed answers.

Nothing to set up. The key below is the public sandbox key. It needs no
account, and every call returns the same sample data each time: five fixed
cases, each frozen at one point in its life. Nothing is stored, emailed or
filed, so run this as often as you like.

What it walks through:

1. Create a case (POST /cases). The request is checked like a real one,
   then the fixed "just created" case comes back.
2. Check the status of a case (GET /cases/{id}): one that is waiting on the
   other party, and one that is waiting on you.
3. Fetch the other party's response and evidence: the event that says they
   answered, their response, the list of files, and one download.
4. Submit your response and evidence (POST /cases/{id}/responses), and see
   the 409 a response gets when it is not your turn.
5. Read the award on a decided case (GET /cases/{id}/decision).
6. The same calls from the respondent's side.

To move from the sandbox to real filings, change API_KEY to your own key
and use the case ids the API gives you. Everything else stays the same.

Run it:

    pip install requests
    python sandbox_walkthrough.py
"""

import os
import tempfile

import requests

# The public sandbox key. Swap in your own key to call the live API.
API_KEY = "dvarb_sandbox_public_key"

# Base URL of the DecisionLayer API. Use http://localhost:8000 locally.
BASE_URL = "https://www.decisionlayer.ai"

API = f"{BASE_URL}/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
TIMEOUT = 60

# The five fixed sandbox cases.
CASE_CREATED = "case_00000000000000000000000001"  # what POST /cases returns
CASE_WAITING = "case_00000000000000000000000002"  # waiting on the respondent
CASE_REPLY_DUE = "case_00000000000000000000000003"  # they answered; your turn
CASE_RESPONDENT = "case_00000000000000000000000004"  # you are the respondent
CASE_DECIDED = "case_00000000000000000000000005"  # decided; award published


def show(title: str) -> None:
    print(f"\n=== {title} ===")


def check(response: requests.Response, expected: int) -> None:
    """Stop with the API's own explanation if a call did not go as expected."""
    if response.status_code != expected:
        raise SystemExit(
            f"{response.request.method} {response.url} returned "
            f"{response.status_code}, expected {expected}:\n{response.text}"
        )


def post_form(path: str, fields: dict, attachments: list) -> requests.Response:
    """POST multipart/form-data, the only body the write calls accept.

    ``attachments`` is a list of (field, filename, text). Text fields are
    sent as parts too, so the request is multipart even with no files.
    """
    parts = [(name, (None, value)) for name, value in fields.items()]
    parts += [
        (field, (filename, text, "text/plain")) for field, filename, text in attachments
    ]
    return requests.post(f"{API}{path}", headers=HEADERS, files=parts, timeout=TIMEOUT)


def describe(case: dict) -> None:
    step = case["next_step"]
    print(f"  {case['id']}: {case['status']} (you are the {case['role']})")
    print(f"  {case['status_summary']}")
    print(f"  action_required={case['action_required']}, next: {step['description']}")
    if step["api"]:
        print(f"  call: {step['api']}")


# --- 1. Create a case ---------------------------------------------------------

show("1. Create a case")
contract = (
    "Sample services agreement. Any dispute arising under this agreement "
    "will be resolved by binding arbitration administered by DecisionLayer."
)
created = post_form(
    "/cases",
    {
        "question_for_arbitration": "Respondent kept a $3,500 deposit.",
        "argument": "The contract required return of the deposit within 30 days.",
        "financial_demand_usd": "3500.00",
        "respondent_first_name": "Jordan",
        "respondent_last_name": "Chen",
        "respondent_email": "jordan.chen@example.com",
        "respondent_street_address": "12 Main St",
        "respondent_city": "Austin",
        "respondent_state": "TX",
        "respondent_zipcode": "78701",
        "claimant_street_address": "800 Oak Ave",
        "claimant_city": "Denver",
        "claimant_state": "CO",
        "claimant_zipcode": "80202",
        "claimant_affirmation": "true",
    },
    [("contract_file", "services_agreement.txt", contract)],
)
check(created, 201)
print(f"  sandbox header: {created.headers.get('X-DecisionLayer-Sandbox')}")
print(f"  {created.json()['message']}")
describe(created.json()["case"])

# The request is checked before the fixed answer is returned. Leave out the
# contract and the affirmation and you get the 422 the live API would send.
rejected = post_form(
    "/cases",
    {
        "question_for_arbitration": "Respondent kept a $3,500 deposit.",
        "argument": "The contract required return of the deposit within 30 days.",
        "respondent_first_name": "Jordan",
        "respondent_email": "jordan.chen@example.com",
    },
    [],
)
check(rejected, 422)
print("  A filing with fields missing is a 422 that lists every problem:")
for detail in rejected.json()["error"]["details"][:3]:
    print(f"    - {detail}")

# --- 2. Check the status of a case --------------------------------------------

show("2. Check status")
for case_id in (CASE_WAITING, CASE_REPLY_DUE):
    case = requests.get(f"{API}/cases/{case_id}", headers=HEADERS, timeout=TIMEOUT)
    check(case, 200)
    describe(case.json())

needs_me = requests.get(
    f"{API}/cases",
    headers=HEADERS,
    params={"action_required": "true"},
    timeout=TIMEOUT,
)
check(needs_me, 200)
print(f"  cases waiting on you: {[case['id'] for case in needs_me.json()]}")

# --- 3. Fetch the other party's response and evidence -------------------------

show("3. The other party responded")
# Poll GET /events with the cursor from your last call. Each event says what
# changed and where its case stands now, so there is nothing to compare.
feed = requests.get(f"{API}/events", headers=HEADERS, timeout=TIMEOUT)
check(feed, 200)
page = feed.json()
answered = [
    event
    for event in page["events"]
    if event["resource_id"] == CASE_REPLY_DUE
    and event["type"] in ("case.response_submitted", "case.review_completed")
]
# The last two: the respondent's response, then the turn passing to you.
for event in answered[-2:]:
    print(f"  {event['occurred_at']}  {event['type']}: {event['summary']}")
print(f"  store this and send it as ?cursor= next time: {page['next_cursor']}")

thread = requests.get(
    f"{API}/cases/{CASE_REPLY_DUE}/responses", headers=HEADERS, timeout=TIMEOUT
)
check(thread, 200)
for round_ in thread.json():
    print(
        f"  round {round_['round']} by the {round_['submitted_by']}: "
        f"{len(round_['argument'])} characters of argument, "
        f"{len(round_['evidence_files'])} files"
    )
    if round_["evidence_demands"]:
        print(f"  they ask you to produce: {round_['evidence_demands']}")

files = requests.get(
    f"{API}/cases/{CASE_REPLY_DUE}/files", headers=HEADERS, timeout=TIMEOUT
)
check(files, 200)
for item in files.json():
    print(f"  {item['step']:<20} {item['kind']:<11} {item['name']}")

# Every file has a download_url. It takes the same Authorization header.
theirs = next(item for item in files.json() if item["submitted_by"] == "respondent")
download = requests.get(
    theirs["download_url"], headers=HEADERS, stream=True, timeout=TIMEOUT
)
check(download, 200)
saved = os.path.join(tempfile.mkdtemp(prefix="decisionlayer-"), theirs["name"])
with open(saved, "wb") as handle:
    for chunk in download.iter_content(chunk_size=65536):
        handle.write(chunk)
print(f"  saved {os.path.getsize(saved)} bytes to {saved}")

# --- 4. Submit your response and evidence -------------------------------------

show("4. Submit your response")
case = requests.get(
    f"{API}/cases/{CASE_REPLY_DUE}", headers=HEADERS, timeout=TIMEOUT
).json()
print(f"  round {case['next_round']} accepts: {', '.join(case['accepted_fields'])}")
reply = post_form(
    f"/cases/{CASE_REPLY_DUE}/responses",
    {
        "argument": "The accepted purchase order sets the price.",
        "evidence_response": "The record you asked for is attached.",
        "affirmation": "true",
    },
    [
        ("evidence", "service_report.txt", "Sample evidence."),
        ("evidence_response_files", "allocation_record.txt", "Sample record."),
    ],
)
check(reply, 201)
submitted = reply.json()
print(
    f"  recorded {submitted['response']['id']} with "
    f"{len(submitted['response']['evidence_files'])} files"
)
describe(submitted["case"])

# A response when it is not your turn is a 409 that says whose turn it is.
too_soon = post_form(
    f"/cases/{CASE_WAITING}/responses",
    {"argument": "Too soon.", "affirmation": "true"},
    [],
)
check(too_soon, 409)
print(f"  not your turn: {too_soon.json()['error']['details'][0]}")

# --- 5. Read the award --------------------------------------------------------

show("5. Read the award")
decision = requests.get(
    f"{API}/cases/{CASE_DECIDED}/decision", headers=HEADERS, timeout=TIMEOUT
)
check(decision, 200)
award = decision.json()
print(f"  published {award['published_at']}, PDF at {award['pdf_url']}")
print(f"  {award['text'].split('AWARD:')[-1].strip().splitlines()[0]}")

# Until a case is decided, the same call is a 404 with reason not_published.
pending = requests.get(
    f"{API}/cases/{CASE_REPLY_DUE}/decision", headers=HEADERS, timeout=TIMEOUT
)
check(pending, 404)
print(f"  before the award: reason={pending.json()['error']['reason']}")

# --- 6. The respondent's side -------------------------------------------------

show("6. If you are the respondent")
mine = requests.get(f"{API}/cases/{CASE_RESPONDENT}", headers=HEADERS, timeout=TIMEOUT)
check(mine, 200)
describe(mine.json())
answer = post_form(
    f"/cases/{CASE_RESPONDENT}/responses",
    {
        "argument": "The price in our records is lower.",
        "evidence_demands": "Your credit allocation record.",
        "affirmation": "true",
    },
    [("evidence", "receiving_log.txt", "Sample evidence.")],
)
check(answer, 201)
describe(answer.json()["case"])

print("\nDone. Every call above returns the same answer each time you run it.")
