"""
Semantic Grounding Benchmark (SGB) - synthetic world.

Deterministic. Two organisations that both describe the same physical events,
each locally correct, with definitions that changed over time.

Meridian Supply Co  - "our" organisation
Halden Logistics    - counterparty (supplier / carrier)

All money is whole units to keep arithmetic noise out of the measurement.
"""

import random
from datetime import date, timedelta

SEED = 7
TODAY = date(2026, 9, 6)

SEGMENTS = ["SMB", "MID", "ENT"]
SUPPLIERS = ["HAL", "ZEN"]  # HAL = Halden Logistics (contract C-118)

# Public holidays used by the two business-day calendars (2026 only, subset)
MERIDIAN_HOLIDAYS = {
    date(2026, 1, 1), date(2026, 3, 25), date(2026, 4, 10), date(2026, 4, 13),
    date(2026, 5, 1), date(2026, 6, 1), date(2026, 8, 15), date(2026, 10, 1),
}
HALDEN_HOLIDAYS = {
    date(2026, 1, 1), date(2026, 1, 6), date(2026, 4, 3), date(2026, 4, 6),
    date(2026, 5, 1), date(2026, 6, 8), date(2026, 8, 15), date(2026, 12, 26),
}


def _d(y, m, dd):
    return date(y, m, dd)


def build_world():
    rnd = random.Random(SEED)

    customers = []
    for i in range(1, 25):
        cid = "C%03d" % i
        signup = _d(2024, 1, 1) + timedelta(days=rnd.randint(0, 700))
        customers.append({
            "customer_id": cid,
            "name": "Customer %02d" % i,
            "signup_date": signup,
            "segment": rnd.choice(SEGMENTS),
            "contract_status": "open" if rnd.random() < 0.75 else "closed",
        })

    orders = []
    start = _d(2025, 6, 1)
    span = (_d(2026, 8, 31) - start).days
    for i in range(1, 73):
        oid = "O%03d" % i
        c = rnd.choice(customers)
        cust = c["customer_id"]
        lo = max(start, c["signup_date"])
        if (_d(2026, 8, 31) - lo).days <= 0:
            lo = start
        odate = lo + timedelta(days=rnd.randint(0, (_d(2026, 8, 31) - lo).days))
        promised = odate + timedelta(days=rnd.randint(7, 21))
        revised = None
        if rnd.random() < 0.30:
            revised = promised + timedelta(days=rnd.randint(3, 10))
        r = rnd.random()
        status = "cancelled" if r < 0.08 else ("open" if r < 0.25 else "invoiced")
        gross = rnd.randint(20, 180) * 100
        discount = int(gross * rnd.choice([0.0, 0.0, 0.05, 0.10, 0.15]))
        freight = rnd.randint(1, 9) * 50
        tax = int((gross - discount) * 0.19)
        sup = rnd.choice(SUPPLIERS)
        if sup == "HAL":
            contract = "C-118" if rnd.random() < 0.65 else "C-301"
        else:
            contract = "C-204"
        orders.append({
            "order_id": oid,
            "customer_id": cust,
            "order_date": odate,
            "promised_date": promised,
            "revised_promised_date": revised,
            "status": status,
            "gross_amount": gross,
            "discount_amount": discount,
            "freight_amount": freight,
            "tax_amount": tax,
            "supplier_id": sup,
            "contract_id": contract,
        })

    shipments = []
    for o in orders:
        if o["status"] == "cancelled":
            continue
        base = o["revised_promised_date"] or o["promised_date"]
        scan = base + timedelta(days=rnd.randint(-6, 8))
        if scan > TODAY:
            scan = TODAY - timedelta(days=rnd.randint(1, 20))
        confirm = None
        if rnd.random() < 0.78:
            confirm = scan + timedelta(days=rnd.randint(1, 4))
            if confirm > TODAY:
                confirm = None
        shipments.append({
            "shipment_id": "S%03d" % (len(shipments) + 1),
            "order_id": o["order_id"],
            "supplier_id": o["supplier_id"],
            "carrier_scan_date": scan,
            "customer_confirm_date": confirm,
        })

    ship_by_order = {s["order_id"]: s for s in shipments}

    invoices = []
    for o in orders:
        if o["status"] != "invoiced":
            continue
        s = ship_by_order.get(o["order_id"])
        if not s:
            continue
        idate = s["carrier_scan_date"] + timedelta(days=rnd.randint(1, 5))
        if idate > TODAY:
            idate = TODAY
        invoices.append({
            "invoice_id": "I%03d" % (len(invoices) + 1),
            "order_id": o["order_id"],
            "invoice_date": idate,
            "amount": o["gross_amount"] - o["discount_amount"] + o["freight_amount"] + o["tax_amount"],
        })

    inv_orders = [inv["order_id"] for inv in invoices]
    returns = []
    for oid in rnd.sample(inv_orders, 10):
        o = next(x for x in orders if x["order_id"] == oid)
        inv = next(x for x in invoices if x["order_id"] == oid)
        rdate = inv["invoice_date"] + timedelta(days=rnd.randint(3, 40))
        if rdate > TODAY:
            rdate = TODAY
        returns.append({
            "return_id": "R%03d" % (len(returns) + 1),
            "order_id": oid,
            "return_date": rdate,
            "amount": int((o["gross_amount"] - o["discount_amount"]) * rnd.choice([0.25, 0.5, 1.0])),
        })

    return {
        "customers": customers,
        "orders": orders,
        "shipments": shipments,
        "invoices": invoices,
        "returns": returns,
    }


# --------------------------------------------------------------------------
# Rendering: the raw data every arm receives, identical across arms.
# --------------------------------------------------------------------------

def _fmt(v):
    if v is None:
        return ""
    if isinstance(v, date):
        return v.isoformat()
    return str(v)


def render_table(name, rows, cols):
    out = ["# " + name, ",".join(cols)]
    for r in rows:
        out.append(",".join(_fmt(r[c]) for c in cols))
    return "\n".join(out)


def render_data(w):
    parts = [
        render_table("customers", w["customers"],
                     ["customer_id", "signup_date", "segment", "contract_status"]),
        render_table("orders", w["orders"],
                     ["order_id", "customer_id", "order_date", "promised_date",
                      "revised_promised_date", "status", "gross_amount", "discount_amount",
                      "freight_amount", "tax_amount", "supplier_id", "contract_id"]),
        render_table("shipments", w["shipments"],
                     ["shipment_id", "order_id", "supplier_id", "carrier_scan_date",
                      "customer_confirm_date"]),
        render_table("invoices", w["invoices"],
                     ["invoice_id", "order_id", "invoice_date", "amount"]),
        render_table("returns", w["returns"],
                     ["return_id", "order_id", "return_date", "amount"]),
    ]
    return "\n\n".join(parts)


# --------------------------------------------------------------------------
# Ground-truth engine: each definition implemented exactly once, in code.
# --------------------------------------------------------------------------

def active_customers(w, as_of, window_days):
    """Definition def.active_customer. Non-cancelled order within window before as_of."""
    lo = as_of - timedelta(days=window_days)
    ids = set()
    for o in w["orders"]:
        if o["status"] == "cancelled":
            continue
        if lo < o["order_date"] <= as_of:
            ids.add(o["customer_id"])
    return len(ids)


def net_revenue(w, d_from, d_to, include_freight=False):
    """def.net_revenue. v1 (2025-01-01..2026-04-30) excludes freight and tax.
    v2 (from 2026-05-01) includes freight, still excludes tax.
    Returns dated in the period are deducted in both versions."""
    by_order = {o["order_id"]: o for o in w["orders"]}
    total = 0
    for inv in w["invoices"]:
        if d_from <= inv["invoice_date"] <= d_to:
            o = by_order[inv["order_id"]]
            total += o["gross_amount"] - o["discount_amount"]
            if include_freight:
                total += o["freight_amount"]
    for r in w["returns"]:
        if d_from <= r["return_date"] <= d_to:
            total -= r["amount"]
    return total


def delivery_date(shipment, scope):
    """scope 'meridian' -> customer confirmation; scope 'halden' -> carrier scan."""
    if scope == "meridian":
        return shipment["customer_confirm_date"]
    return shipment["carrier_scan_date"]


def delivered_count(w, as_of, scope, supplier=None, contract=None):
    by_order = {o["order_id"]: o for o in w["orders"]}
    n = 0
    for s in w["shipments"]:
        o = by_order[s["order_id"]]
        if supplier and s["supplier_id"] != supplier:
            continue
        if contract and o["contract_id"] != contract:
            continue
        dd = delivery_date(s, scope)
        if dd is not None and dd <= as_of:
            n += 1
    return n


def on_time_count(w, as_of, delivered_scope, promise_version, supplier=None, contract=None):
    """promise_version 1 -> original promised_date; 2 -> revised if present."""
    by_order = {o["order_id"]: o for o in w["orders"]}
    n = 0
    for s in w["shipments"]:
        o = by_order[s["order_id"]]
        if supplier and s["supplier_id"] != supplier:
            continue
        if contract and o["contract_id"] != contract:
            continue
        dd = delivery_date(s, delivered_scope)
        if dd is None or dd > as_of:
            continue
        target = o["promised_date"]
        if promise_version == 2 and o["revised_promised_date"]:
            target = o["revised_promised_date"]
        if dd <= target:
            n += 1
    return n


def order_value(o, scope):
    """meridian: excludes freight and tax. halden: includes freight, excludes tax."""
    v = o["gross_amount"] - o["discount_amount"]
    if scope == "halden":
        v += o["freight_amount"]
    return v


def sum_order_value(w, d_from, d_to, scope, supplier=None, contract=None):
    total = 0
    for o in w["orders"]:
        if o["status"] == "cancelled":
            continue
        if supplier and o["supplier_id"] != supplier:
            continue
        if contract and o["contract_id"] != contract:
            continue
        if d_from <= o["order_date"] <= d_to:
            total += order_value(o, scope)
    return total


def business_days_between(a, b, calendar):
    """Count business days strictly after a, up to and including b."""
    hol = MERIDIAN_HOLIDAYS if calendar == "meridian" else HALDEN_HOLIDAYS
    n = 0
    cur = a + timedelta(days=1)
    while cur <= b:
        if cur.weekday() < 5 and cur not in hol:
            n += 1
        cur += timedelta(days=1)
    return n
