"""The mgf-logship wire protocol — versioned + language-neutral.

The contract between any shipper (Python :class:`~mgf.logship.LogShipper`, or a
future Zig/TS shipper) and any collector. Deliberately boring: HTTP + NDJSON,
so a non-Python runtime can ship logs with nothing but a socket and a JSON
encoder. Source of truth for the framing; the *record* shape inside is
mgf-common's canonical ``LogRecord`` (OB-10), unchanged.

Endpoints (collector side):

  ``POST /ingest``  — body is **NDJSON**: one canonical ``LogRecord`` JSON
                      object per line (byte-identical to what every runtime
                      emits — the line is NOT re-wrapped, so the cross-language
                      contract is preserved end-to-end). ``204`` on accept.
  ``POST /stats``   — body is a single ``LogStats`` JSON object. ``204``.
  ``GET  /health``  — liveness; ``200`` ``{"status":"ok"}``.

Headers on every ``POST``:

  ``Authorization: Bearer <token>``  — required if the collector is tokened.
  ``Content-Type: application/x-ndjson`` (ingest) / ``application/json`` (stats).
  ``X-Logship-Protocol: <version>``  — this module's :data:`SHIP_PROTOCOL_VERSION`.
  ``X-Logship-App: <app>``           — the originating app name.
  ``X-Logship-Batch: <uuid>``        — idempotency / de-dup key for the batch.

Versioning mirrors OB-09's additive discipline: new optional headers / fields
are non-breaking; renaming/removing one bumps the MAJOR of
:data:`SHIP_PROTOCOL_VERSION`.
"""

from __future__ import annotations

import json
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Iterable

SHIP_PROTOCOL_VERSION = "1.0.0"

INGEST_PATH = "/ingest"
STATS_PATH = "/stats"
HEALTH_PATH = "/health"

NDJSON_CONTENT_TYPE = "application/x-ndjson"
JSON_CONTENT_TYPE = "application/json"

HEADER_PROTOCOL = "X-Logship-Protocol"
HEADER_APP = "X-Logship-App"
HEADER_BATCH = "X-Logship-Batch"


def encode_ndjson(records: Iterable[dict[str, object]]) -> bytes:
    """Encode an iterable of wire dicts as an NDJSON request body.

    Each line is compact + raw UTF-8 (matching ``LogRecord.to_jsonl_line``),
    so a batch is just the concatenation of canonical record lines.

    Example::

        wire = encode_ndjson([record_dict], stats={"shipped": 1})
    """
    out = "".join(
        json.dumps(r, separators=(",", ":"), ensure_ascii=False, default=str) + "\n"
        for r in records
    )
    return out.encode("utf-8")


def decode_ndjson(body: bytes | str) -> list[dict[str, object]]:
    """Parse an NDJSON body into wire dicts; malformed/blank lines are skipped."""
    text = body.decode("utf-8", "replace") if isinstance(body, bytes) else body
    records: list[dict[str, object]] = []
    for line in text.splitlines():
        line = line.strip()
        if not line:
            continue
        try:
            obj = json.loads(line)
        except ValueError:
            continue
        if isinstance(obj, dict):
            records.append(obj)
    return records


__all__ = [
    "HEADER_APP",
    "HEADER_BATCH",
    "HEADER_PROTOCOL",
    "HEALTH_PATH",
    "INGEST_PATH",
    "JSON_CONTENT_TYPE",
    "NDJSON_CONTENT_TYPE",
    "SHIP_PROTOCOL_VERSION",
    "STATS_PATH",
    "decode_ndjson",
    "encode_ndjson",
]
