"""The marketplace connector — our own marketplace first (NEOMARK / `neo-register`).

The register's law is five events and only five: `LISTING_PUBLISHED → RFQ_CREATED →
OFFER_SUBMITTED → HANDSHAKE_MADE → OUTCOME_RECORDED`. The funnel lives IN the register,
deliberately — and it stays there. A company's book records TRADES, not funnels, so this
connector translates exactly ONE of the five into a record: **`HANDSHAKE_MADE` becomes the
relation's `CONTRACT`** — the deal both parties said yes to, which is precisely what the
grammar's `CONTRACT` kind is. The other four events map to no canonical kind and are
refused with that reason, not silently dropped.

Two register concepts need a deterministic projection to fit the grammar, and both are
symmetric (both parties' edges compute the same value, so pairing survives):

  * **the reference** — a register `ref` (the handshake's `txn_id`) may be longer than a
    document number. When it fits the grammar's document-number shape it is carried
    verbatim; when it does not, it becomes `MKT-` + the first 24 hex of its SHA-256.
    Deterministic, collision-safe at this scale, and printed on both sides identically.
  * **the timestamp** — `occurred_at` is an instant; a document carries a date. The date
    part is taken as written, never recomputed through a timezone.

What this connector deliberately does NOT do: touch keys or handshakes (`txn_id`,
`terms_hash` and the vote digests are produced at the edge and the register only carries
them — this module carries the reference back, nothing more), and it does not invent
amounts — an amount rides only when the event's payload carries one.
"""

from __future__ import annotations

import hashlib
import re

from .base import AdapterError, ErpAdapter, FieldMap
from ..grammar import REFUSED_BAD_FIELD

NEOMARK_SYSTEM = "NEOMARK"

_DOC_NO_SHAPE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9/_-]{0,34}$")

NEOMARK = FieldMap(
    system_id=NEOMARK_SYSTEM,
    company_code=("company_code",),          # injected by to_draft — the register has none
    document_no=("ref",),
    fiscal_year=(),                          # derived from the date, as the base allows
    doc_date=("occurred_at",),
    amount=("amount",),
    currency=("currency",),
    doc_type=("event_type",),
    doc_kinds={"HANDSHAKE_MADE": "CONTRACT"},
)


def contract_no_for(ref: str) -> str:
    """The register ref as a document number — verbatim when it fits, projected when not.

    The projection is the connector's ONE piece of logic, and it is symmetric: both
    parties hold the same register ref, so both derive the same contract number without
    talking — the property every shared handle in the grammar must have.
    """
    if not isinstance(ref, str) or not ref:
        raise AdapterError(REFUSED_BAD_FIELD, f"{NEOMARK_SYSTEM}: event ref must be a non-empty string")
    if _DOC_NO_SHAPE.match(ref):
        return ref
    return "MKT-" + hashlib.sha256(ref.encode("utf-8")).hexdigest()[:24]


class MarketplaceAdapter(ErpAdapter):
    """`ErpAdapter` over a register event: normalise the register's shapes, then let the
    ordinary table machinery do everything else. Logic stays contained to the two
    projections named in the module docstring."""

    def to_draft(self, native: dict, *, amount_basis: str | None = None) -> dict:
        native = dict(native)
        if "ref" in native:
            native["ref"] = contract_no_for(native["ref"])
        occurred = native.get("occurred_at")
        if isinstance(occurred, str) and len(occurred) >= 10:
            native["occurred_at"] = occurred[:10]
        payload = native.get("payload") or {}
        if isinstance(payload, dict):
            for name in ("amount", "currency"):
                if name in payload and name not in native:
                    native[name] = payload[name]
        native.setdefault("company_code", "MKT")
        native.pop("payload", None)
        return super().to_draft(native, amount_basis=amount_basis)


def marketplace_adapter() -> MarketplaceAdapter:
    """The NEOMARK connector, ready to hand to a `NEOedge` — or to use beside the ERP
    adapter for the marketplace-originated slice of a company's trades."""
    return MarketplaceAdapter(NEOMARK)


def relation_seed(event: dict) -> dict:
    """What `open_relation` needs, straight from a `HANDSHAKE_MADE` event: the contract
    number (projected exactly as the record will carry it) and the counterparty. Role is
    the company's own knowledge — a register event cannot assert which side of the trade
    the reader is on."""
    if event.get("event_type") != "HANDSHAKE_MADE":
        raise AdapterError(
            REFUSED_BAD_FIELD,
            f"{NEOMARK_SYSTEM}: a relation opens from HANDSHAKE_MADE, not "
            f"{event.get('event_type')!r} — the funnel stays in the register",
        )
    return {"contract_no": contract_no_for(event.get("ref", "")),
            "counterparty_id": event.get("counterparty")}
