"""Cross-language conformance contract for :class:`LogRecord` (Wave 1.5).

`LogRecord` is the one wire shape **every** Magogi runtime emits — Python
(native), Zig (``hrb-*``), and TS/SvelteKit (decision **D1**: all runtimes
from the start). This module is the *authority* that pins that shape so the
three emitters cannot drift apart:

- :func:`canonical_records` — a curated, **deterministic** set of
  :class:`LogRecord`s covering every corner of the schema (minimal, full,
  error, the non-Python ``lang`` values, the forward-compat ``extra``
  pass-through). No clocks, no hostnames — every value is fixed, so the
  serialisation is byte-stable and can be checked in as a golden.
- :func:`log_schema_manifest` — a **language-neutral** description of the
  schema (version, the always-present core keys, the full emit order, each
  field's type / optionality / omit-when-empty rule, and the serialisation
  rules). A Zig or TS porter reads this — not the Python source — to build a
  conforming emitter.

The two golden artifacts generated from this module
(``schema/log_record_fixture.jsonl`` + ``schema/log_record_schema.json``)
ship in the wheel and are what the Zig/TS conformance tests load. Regenerate
them with ``tools/gen_log_record_schema.py``; ``test_log_record_conformance``
fails if they drift from this module or from the live dataclass (OB-10).
"""

from __future__ import annotations

import json
from importlib import resources
from typing import TYPE_CHECKING

from mgf.common.observability.log_record import LOG_SCHEMA_VERSION, LogRecord

if TYPE_CHECKING:
    from pathlib import Path

# Always-present keys (emitted even when empty) — the order :meth:`LogRecord.to_dict`
# writes the core block. Kept here as the cross-language contract; the
# conformance test pins it against the live dataclass.
CORE_KEYS: tuple[str, ...] = (
    "ts",
    "level",
    "level_no",
    "logger",
    "msg",
    "app",
    "lang",
    "schema_version",
)

# Optional typed keys, in the exact order :meth:`LogRecord.to_dict` emits them
# when set (omitted when empty/None/0). Then `fields`, then `labels`, then any
# forward-compat `extra` keys.
OPTIONAL_KEYS: tuple[str, ...] = (
    "module",
    "func",
    "line",
    "thread",
    "process",
    "host",
    "app_version",
    "env",
    "trace_id",
    "span_id",
    "request_id",
    "exception",
    "stack",
)

# Full deterministic emit order (core + optional + maps). The fixture lines
# and every porter MUST emit keys in this order so lines are byte-identical
# across languages.
EMIT_ORDER: tuple[str, ...] = (*CORE_KEYS, *OPTIONAL_KEYS, "fields", "labels")

# Per-field type tags for the language-neutral manifest. "string?" / "int?"
# denote nullable (absent when None); "int0"/"string-empty" denote
# omit-when-zero/empty. Core keys are never omitted.
_FIELD_TYPES: dict[str, str] = {
    "ts": "string",
    "level": "string",
    "level_no": "int",
    "logger": "string",
    "msg": "string",
    "app": "string",
    "lang": "string",
    "schema_version": "string",
    "module": "string?empty",
    "func": "string?empty",
    "line": "int?zero",
    "thread": "string?empty",
    "process": "int?zero",
    "host": "string?empty",
    "app_version": "string?empty",
    "env": "string?null",
    "trace_id": "string?null",
    "span_id": "string?null",
    "request_id": "string?null",
    "exception": "string?null",
    "stack": "string?null",
    "fields": "object?empty",
    "labels": "map<string,string>?empty",
}


def canonical_records() -> list[LogRecord]:
    """The curated, deterministic conformance set — one entry per schema corner.

    Every field is a fixed literal (no ``now()``, no ``gethostname()``), so the
    serialised lines are byte-stable and usable as a cross-language golden.
    """
    return [
        # 1. Minimal — only the always-present core keys (everything else default).
        LogRecord(
            ts="2026-05-28T00:00:00+00:00",
            level="INFO",
            level_no=20,
            logger="app",
            msg="service started",
            app="demo",
            lang="python",
        ),
        # 2. Full — every typed optional set, plus structured fields + labels.
        LogRecord(
            ts="2026-05-28T00:00:01.500000+00:00",
            level="WARNING",
            level_no=30,
            logger="app.db",
            msg="slow query",
            module="pool",
            func="acquire",
            line=87,
            thread="MainThread",
            process=4321,
            host="host-1",
            app="demo",
            app_version="1.2.3",
            env="staging",
            trace_id="0af7651916cd43dd8448eb211c80319c",
            span_id="b7ad6b7169203331",
            request_id="req-42",
            fields={"duration_ms": 1234, "rows": 0, "cached": False},
            labels={"region": "eu-west-1", "tier": "db"},
        ),
        # 3. Error — exception + stack, ERROR level.
        LogRecord(
            ts="2026-05-28T00:00:02+00:00",
            level="ERROR",
            level_no=40,
            logger="app.http",
            msg="request failed",
            host="host-1",
            app="demo",
            app_version="1.2.3",
            exception="Traceback (most recent call last):\n  ...\nValueError: boom",
            stack='  File "app/http.py", line 12, in handle\n    raise ValueError',
        ),
        # 4. Zig runtime — non-Python `lang`, structured fields.
        LogRecord(
            ts="2026-05-28T00:00:03+00:00",
            level="INFO",
            level_no=20,
            logger="hrb.kabi",
            msg="device attached",
            host="host-1",
            app="hrb-daemon",
            app_version="0.4.0",
            lang="zig",
            fields={"vid": 1133, "pid": 49374},
        ),
        # 5. TS runtime (server-side) — non-Python `lang`, request correlation.
        LogRecord(
            ts="2026-05-28T00:00:04+00:00",
            level="DEBUG",
            level_no=10,
            logger="web.ssr",
            msg="render route",
            host="host-1",
            app="magogi-website",
            app_version="2026.5.0",
            lang="ts",
            request_id="req-99",
            fields={"route": "/dashboard", "ttfb_ms": 12},
        ),
        # 6. Forward-compat — carries a key this schema version does NOT know
        #    (`severity_text`), which MUST survive a round-trip via `extra`
        #    (OB-09 rule 3 — an older relay never strips a newer producer's data).
        LogRecord(
            ts="2026-05-28T00:00:05+00:00",
            level="INFO",
            level_no=20,
            logger="app",
            msg="future field carried through",
            app="demo",
            lang="python",
            extra={"severity_text": "notice"},
        ),
    ]


def log_schema_manifest() -> dict[str, object]:
    """A language-neutral description of the ``LogRecord`` wire schema.

    The contract a Zig or TS porter builds against without reading Python:
    the schema version, the always-present ``core`` keys, the full ``emit_order``,
    each field's type + optionality, and the serialisation ``rules`` (compact
    separators, the omit-when-empty sentinels, and the forward-compat policy).
    """
    return {
        "schema_version": LOG_SCHEMA_VERSION,
        "core": list(CORE_KEYS),
        "emit_order": list(EMIT_ORDER),
        "fields": {key: _FIELD_TYPES[key] for key in EMIT_ORDER},
        "rules": {
            "encoding": "UTF-8, one JSON object per line (JSONL), trailing newline",
            "separators": [",", ":"],
            "ensure_ascii": False,
            "key_order": "exactly `emit_order`; `extra` (unknown) keys appended after",
            "omit_when_empty": {
                "string?empty": 'omitted when ""',
                "int?zero": "omitted when 0",
                "string?null": "omitted when null",
                "object?empty": "omitted when {}",
                "map<string,string>?empty": "omitted when {}",
            },
            "core_always_present": True,
            "unknown_keys": "preserved verbatim through a round-trip (OB-09 rule 3)",
        },
    }


# --------------------------------------------------------------------------- #
# Golden artifact generation + lookup (used by tools/ + the conformance test)
# --------------------------------------------------------------------------- #

FIXTURE_RESOURCE = "log_record_fixture.jsonl"
SCHEMA_RESOURCE = "log_record_schema.json"
_SCHEMA_PKG = "mgf.common.observability.schema"


def render_fixture() -> str:
    """The canonical fixture file body — one JSONL line per canonical record."""
    return "".join(rec.to_jsonl_line() for rec in canonical_records())


def render_schema_json() -> str:
    """The canonical manifest file body (pretty JSON + trailing newline)."""
    return json.dumps(log_schema_manifest(), indent=2, sort_keys=False) + "\n"


def fixture_path() -> Path:
    """Filesystem path to the packaged golden fixture (for porters/tests)."""
    return resources.files(_SCHEMA_PKG).joinpath(FIXTURE_RESOURCE)  # type: ignore[return-value]


def schema_path() -> Path:
    """Filesystem path to the packaged golden manifest (for porters/tests)."""
    return resources.files(_SCHEMA_PKG).joinpath(SCHEMA_RESOURCE)  # type: ignore[return-value]


__all__ = [
    "CORE_KEYS",
    "EMIT_ORDER",
    "OPTIONAL_KEYS",
    "canonical_records",
    "log_schema_manifest",
    "render_fixture",
    "render_schema_json",
]
