// An accounting system kept up with Xero: a contact and an invoice made, authorised and paid, lists
// read to the end, per-record refusals, the rate limits, and webhooks with their intent-to-receive.
//
//   XERO_CLIENT_ID=... XERO_CLIENT_SECRET=... XERO_REFRESH_TOKEN=... XERO_TOKEN_FILE=xero-token \
//   XERO_WEBHOOK_KEY=... quillo run xero.qwe
//   then open /invoice, /batch, /lists, /tokens, /dates, /hook-check and /failures
//
// A refresh token works once, so XERO_TOKEN_FILE keeps the newest. XERO_API_URL and XERO_TOKEN_URL
// reach a test server. Point a webhook at POST /xero/webhook.

use "xero.qwer"

route GET /invoice {
    let c = xero_contact_create({ Name: "Ada Lovelace Ltd", EmailAddress: "ada@example.com" })?
    let inv = xero_invoice_create({ Type: "ACCREC", Contact: { ContactID: c.ContactID }, Date: "2026-11-01", DueDate: "2026-11-30", LineItems: [{ Description: "Mugs", Quantity: 4, UnitAmount: 12.5, AccountCode: "200" }], Status: "DRAFT" }, "inv-1001")?
    let again = xero_invoice_create({ Type: "ACCREC", Contact: { ContactID: c.ContactID }, Date: "2026-11-01", DueDate: "2026-11-30", LineItems: [{ Description: "Mugs", Quantity: 4, UnitAmount: 12.5, AccountCode: "200" }], Status: "DRAFT" }, "inv-1001")?
    let early = xero_payment_create(inv.InvoiceID, "090", 50.0, "2026-11-02").error
    let live = xero_invoice_update(inv.InvoiceID, { Status: "AUTHORISED" })?
    let paid = xero_payment_create(inv.InvoiceID, "090", 50.0, "2026-11-02")?
    let read = xero_invoice(inv.InvoiceID)?
    {
        same: inv.InvoiceID == again.InvoiceID, total: inv.Total, early: early,
        live: live.Status, paid: paid.Amount, state: read.Status, due: read.AmountDue,
        contact: xero_contact(c.ContactID)?.Name,
        twice: xero_contact_create({ Name: "Ada Lovelace Ltd" }).error,
    }
}

route GET /batch {
    let good = { Type: "ACCREC", Contact: { ContactID: "00000000-0000-0000-0000-000000000001" }, LineItems: [{ Description: "A", Quantity: 1, UnitAmount: 1, AccountCode: "200" }] }
    let bad = { Type: "ACCREC", Contact: { ContactID: "00000000-0000-0000-0000-000000000001" }, LineItems: [] }
    let r = xero_invoices_create([good, bad, good], "")?
    { made: len(r.created), failed: len(r.failed), why: r.failed[0].errors, none: xero_invoices_create([], "").error }
}

route GET /lists = {
    invoices: len(xero_invoices({ Statuses: "PAID" }, 0)?),
    capped: len(xero_invoices({ Statuses: "PAID" }, 130)?),
    since: len(xero_invoices({ modified_since: "2026-01-01T00:00:00", Statuses: "PAID" }, 0)?),
    filtered: len(xero_invoices({ where: xero_where("Type", "==", "ACCPAY")?, Statuses: "PAID" }, 0)?),
    contacts: len(xero_contacts({}, 0)?) >= 105,
    payments: len(xero_payments({}, 0)?) >= 0,
    limits: xero_rate_limits(),
}

route GET /where = {
    text: xero_where("Status", "==", "AUTHORISED")?,
    quotes: xero_where("Name", "==", "Ada \"Countess\" L")?,
    date: xero_where("Date", ">=", "2026-01-31")?,
    guid: xero_where("Contact.ContactID", "==", "dfa5ab6c-a6dd-4a5e-b5e4-55ad6f7a0ec5")?,
    number: xero_where("Total", ">", 100)?,
    bad: xero_where("x", "like", "y").error,
}

// The first access token runs out at the stand-in; the second call signs in again by itself.
route GET /tokens {
    let before = xero_session()
    let names = map(xero_connections()?, (c) => c.tenantName)
    let after = xero_session()
    { rotated: after.rotations >= 1, moved: after.refresh_token != env("XERO_REFRESH_TOKEN", ""), before_has_token: before.refresh_token != null, names: names }
}

route GET /dates = {
    json: xero_date("/Date(1518685950940+0000)/")?,
    offset: xero_date("/Date(0+0000)/")?,
    old: xero_date("/Date(-86400000+0000)/")?,
    plain: xero_date("2026-11-01T00:00:00")?,
    bad: xero_date("/Date(soon)/").error,
}

// Xero's first call is an intent-to-receive: 200 and nothing else if signed well, 401 if not.
route POST /xero/webhook {
    if !xero_webhook_valid(request.body, request.headers["x-xero-signature"] ?? "", env("XERO_WEBHOOK_KEY", "")) { reply 401 "" }
    respond(200, "")
}

// A signature made outside this plugin, by node's crypto.
route GET /hook-check {
    let body = "{\"events\":[{\"resourceId\":\"r1\",\"eventCategory\":\"INVOICE\",\"eventType\":\"UPDATE\"}],\"firstEventSequence\":1,\"lastEventSequence\":1,\"entropy\":\"S0m3r4Nd0mt3xt\"}"
    let sig = "UFD5tPMcBLyQfbX11J04mkAlQCX9R36l0Xeozn3j8Zs="
    {
        good: xero_webhook_valid(body, sig, "whk-test"),
        events: map(xero_webhook_events(body, sig, "whk-test")?, (e) => e.eventCategory),
        tampered: xero_webhook_valid(body + " ", sig, "whk-test"),
        other_key: xero_webhook_events(body, sig, "other").error,
        not_base64: xero_webhook_valid(body, "!!", "whk-test"),
    }
}

route GET /failures = {
    missing: xero_invoice("00000000-0000-0000-0000-00000000dead").error,
    waited: xero_call("get", "/Busy", null)?.Status,
    daily: xero_call("get", "/Daily", null).error,
    invalid: xero_invoice_create({ Type: "ACCREC", LineItems: [] }, "").error,
}
