"""The testbed — simulated companies, on different ERPs, trading with each other.

Twenty companies test **semantics, not scale**. That sentence belongs in the first line of
any write-up of a run here: twenty edges is a loop over configuration, and the ledger numbers
that matter were measured elsewhere. What this proves is that two companies on two different
ERP vocabularies produce records that pair, and that every disagreement a real trade can have
is detected from the records alone.

Build for two, generate twenty: the code does not know the difference, so the pair that
proves every claim is the same code path the twenty run on.

The deliberate faults are the product. A run with no fault proves only that agreement is
detectable, which is the easy half.
"""

from __future__ import annotations

from dataclasses import dataclass, field

from .adapters import adapter_for, generic_adapter
from .edge import NEOedge, Relation
from .members import Member, MemberRegistry

# The ERPs the simulated companies run on. Rotating them is the point: if edge A is on SAP
# and edge B is on Oracle and their records still pair byte for byte, the grammar holds.
SYSTEMS = ("SAP-S4HANA", "ORACLE-FUSION", "MS-DYNAMICS-365-FO", "ACME-LEDGER")

# The unfamiliar-ERP case, configured rather than coded — see adapters/generic.py.
ACME_COLUMNS = {
    "company_code": "org", "document_no": "doc_no", "doc_date": "issued",
    "fiscal_year": "year", "amount": "total", "currency": "ccy", "doc_type": "kind",
    "lines": "items", "line_sku": "sku", "line_quantity": "qty",
    "line_unit": "uom", "line_unit_price": "price",
}
ACME_KINDS = {
    "contract": "CONTRACT", "order": "PURCHASE_ORDER", "confirm": "ORDER_CONFIRMATION",
    "dispatch": "DELIVERY_NOTE", "receipt": "GOODS_RECEIPT", "bill": "INVOICE",
    "pay": "PAYMENT", "credit": "CREDIT_MEMO",
}

# Native document-type codes per system, per canonical kind.
_NATIVE_TYPES = {
    "SAP-S4HANA": {"CONTRACT": "MK", "PURCHASE_ORDER": "NB", "ORDER_CONFIRMATION": "AB",
                   "DELIVERY_NOTE": "LF", "GOODS_RECEIPT": "WE", "INVOICE": "RE", "PAYMENT": "KZ"},
    "ORACLE-FUSION": {"CONTRACT": "BLANKET", "PURCHASE_ORDER": "STANDARD",
                      "ORDER_CONFIRMATION": "ACKNOWLEDGMENT", "DELIVERY_NOTE": "SHIPMENT",
                      "GOODS_RECEIPT": "RECEIVE", "INVOICE": "INVOICE", "PAYMENT": "PAYMENT"},
    "MS-DYNAMICS-365-FO": {"CONTRACT": "PurchaseAgreement", "PURCHASE_ORDER": "PurchaseOrder",
                           "ORDER_CONFIRMATION": "OrderConfirmation", "DELIVERY_NOTE": "PackingSlip",
                           "GOODS_RECEIPT": "ProductReceipt", "INVOICE": "VendorInvoice",
                           "PAYMENT": "VendorPayment"},
    "ACME-LEDGER": {k: n for n, k in ACME_KINDS.items()},
}

# Which native field name holds each fact, per system. One row per system keeps the testbed
# honest: it writes documents the way that ERP writes them, not the way NEOedge reads them.
_NATIVE_FIELDS = {
    "SAP-S4HANA": dict(company="BUKRS", number="BELNR", year="GJAHR", date="BLDAT",
                       amount="WRBTR", currency="WAERS", kind="NEOEDGE_DOCTYPE", lines="items",
                       sku="MATNR", qty="MENGE", unit="MEINS", price="NETPR"),
    "ORACLE-FUSION": dict(company="ORG_ID", number="SEGMENT1", year="FISCAL_YEAR",
                          date="TRANSACTION_DATE", amount="INVOICE_AMOUNT",
                          currency="INVOICE_CURRENCY_CODE", kind="DOCUMENT_TYPE_CODE", lines="LINES",
                          sku="ITEM_NUMBER", qty="QUANTITY", unit="UOM_CODE", price="UNIT_PRICE"),
    "MS-DYNAMICS-365-FO": dict(company="DataAreaId", number="PurchId", year="FiscalYear",
                               date="DocumentDate", amount="InvoiceAmount", currency="CurrencyCode",
                               kind="DocumentType", lines="Lines", sku="ItemId", qty="Qty",
                               unit="UnitId", price="UnitPrice"),
    "ACME-LEDGER": dict(company="org", number="doc_no", year="year", date="issued",
                        amount="total", currency="ccy", kind="kind", lines="items",
                        sku="sku", qty="qty", unit="uom", price="price"),
}


def native_document(system_id: str, kind: str, *, company_code: str, document_no: str,
                    doc_date: str, amount: str | None = None, currency: str | None = None,
                    lines: list[dict] | None = None) -> dict:
    """One document in the vendor's own vocabulary — what the ERP would hand over."""
    names = _NATIVE_FIELDS[system_id]
    document = {
        names["company"]: company_code,
        names["number"]: document_no,
        names["year"]: doc_date[:4],
        names["date"]: doc_date,
        names["kind"]: _NATIVE_TYPES[system_id][kind],
    }
    if amount is not None and currency is not None:
        document[names["amount"]] = amount
        document[names["currency"]] = currency
    if lines:
        document[names["lines"]] = [
            {names["sku"]: line["sku"], names["qty"]: line["quantity"], names["unit"]: line["unit"]}
            for line in lines
        ]
    return document


def _adapter(system_id: str):
    if system_id == "ACME-LEDGER":
        return generic_adapter("ACME-LEDGER", ACME_COLUMNS, ACME_KINDS)
    return adapter_for(system_id)


@dataclass
class Company:
    member: Member
    edge: NEOedge
    company_code: str
    system_id: str

    @property
    def member_id(self) -> str:
        return self.member.member_id


@dataclass
class Trade:
    """One completed procure-to-pay thread between two companies."""

    buyer: Company
    seller: Company
    contract_no: str
    buyer_relation: Relation
    seller_relation: Relation
    events: dict = field(default_factory=dict)


def build_network(count: int = 2, *, book_dir=None) -> list[Company]:
    """`count` simulated companies, each on its own ERP, all registered members."""
    registry = MemberRegistry()
    members = []
    for i in range(count):
        system_id = SYSTEMS[i % len(SYSTEMS)]
        member = Member(
            member_id=f"urn:neoedge:member:co{i + 1:02d}",
            legal_name=f"Company {i + 1:02d} d.o.o.",
            country="HR",
            erp_system=system_id,
        )
        registry.register(member)
        members.append((member, system_id))

    companies = []
    for i, (member, system_id) in enumerate(members):
        path = None if book_dir is None else f"{book_dir}/{member.member_id.split(':')[-1]}.jsonl"
        companies.append(Company(
            member=member,
            edge=NEOedge(member.member_id, registry, _adapter(system_id), book_path=path),
            company_code=f"C{i + 1:03d}",
            system_id=system_id,
        ))
    return companies


def run_trade(buyer: Company, seller: Company, contract_no: str, *, fault: str | None = None,
              amount: str = "12500.00", currency: str = "EUR", date: str = "2026-07-20") -> Trade:
    """The full chain — contract, order, confirmation, delivery, receipt, invoice, payment —
    recorded independently by both sides, with one deliberate fault when asked for.

    Faults, each one from the owner's own dispute list:
      `missing_goods_receipt`  the buyer never records the primka, but carries on and pays
      `amount_mismatch`        the two books disagree on what the invoice said
      `partial_delivery`       fewer units arrive than the delivery note claims
      `silent_counterparty`    the seller records nothing at all
      `duplicate_invoice`      the seller issues a second, different invoice for the same event
    """
    buyer_rel = buyer.edge.open_relation(contract_no, seller.member_id, role="BUYER")
    seller_rel = seller.edge.open_relation(contract_no, buyer.member_id, role="SELLER")
    lines = [{"sku": "WIDGET-A", "quantity": "100", "unit": "PCE"}]
    short_lines = [{"sku": "WIDGET-A", "quantity": "80", "unit": "PCE"}]
    events: dict = {}
    relations = {buyer.member_id: buyer_rel, seller.member_id: seller_rel}
    # A silent counterparty records nothing — not the documents it writes, not the ones it gets.
    mute = {seller.member_id} if fault == "silent_counterparty" else set()

    def both(kind: str, originator: Company, doc_no: str, *, other_no: str,
             amount_=amount, currency_=currency, other_amount=None, lines_=None, other_lines=None,
             **extra):
        """The originator writes its own document; the counterparty records its view of the
        same event, carrying the originator's number — that is what makes the two pair."""
        other = seller if originator is buyer else buyer
        for company, number, amt, lns, is_origin in (
            (originator, doc_no, amount_, lines_, True),
            (other, other_no, other_amount or amount_, other_lines or lines_, False),
        ):
            if company.member_id in mute:
                continue
            native = native_document(company.system_id, kind, company_code=company.company_code,
                                     document_no=number, doc_date=date, amount=amt,
                                     currency=currency_ if amt is not None else None, lines=lns)
            entry = company.edge.record(
                native, relations[company.member_id],
                **({} if is_origin else {"originator_doc_no": doc_no}), **extra,
            )
            if is_origin:
                events[kind] = entry.event_key

    both("CONTRACT", seller, contract_no, other_no=f"{contract_no}-IN")
    both("PURCHASE_ORDER", buyer, "PO-A-145", other_no="SO-B-882")
    both("ORDER_CONFIRMATION", seller, "OC-B-901", other_no="OC-A-146")
    both("DELIVERY_NOTE", seller, "DN-B-311", other_no="DN-A-147",
         amount_=None, currency_=None, lines_=lines)
    if fault != "missing_goods_receipt":
        both("GOODS_RECEIPT", buyer, "GR-A-722", other_no="GR-B-312",
             amount_=None, currency_=None,
             # Both books agree the receipt says 80; the shortfall is against the delivery
             # note that says 100 — two consistent books, one real dispute.
             lines_=short_lines if fault == "partial_delivery" else lines,
             fulfills_event=events.get("DELIVERY_NOTE"))
    both("INVOICE", seller, "INV-B-08-2026", other_no="AP-A-5501",
         other_amount="13500.00" if fault == "amount_mismatch" else None)
    both("PAYMENT", buyer, "PAY-A-9001", other_no="BANK-B-77",
         settles_originator_doc_no="INV-B-08-2026")

    if fault == "duplicate_invoice":
        # Same event key, different content: a second invoice under the same number. The book
        # refuses it, which is the correct outcome and the reason it is worth demonstrating.
        duplicate = native_document(seller.system_id, "INVOICE", company_code=seller.company_code,
                                    document_no="INV-B-08-2026", doc_date=date,
                                    amount="14000.00", currency=currency)
        try:
            seller.edge.record(duplicate, seller_rel)
            events["duplicate_accepted"] = True
        except Exception as exc:  # noqa: BLE001 - the refusal itself is the result
            events["duplicate_refused"] = getattr(exc, "code", type(exc).__name__)

    return Trade(buyer, seller, contract_no, buyer_rel, seller_rel, events)
