"""Runnable entrypoint: serve the COMBINED provider (memory + affect) as one ASGI app.

Issue #18 Deliverable 1 — a single endpoint a Worldtree session binds to drive BOTH
planes. Additive: the standalone affect (:8390) + memory (:8391) entrypoints are
unchanged. Config from env:
- RATATOSKR_HEIMDALL_KEY (required): HS256 shared key for the consumer, utf-8.
- RATATOSKR_MEMORY_EMBEDDING_DIM (required): the pinned embedder dim (no default —
  a wrong value silently breaks search).
- RATATOSKR_AFFECT_DB (default "affect.db") + RATATOSKR_MEMORY_DB (default "memory.db"):
  the two SQLite paths (one per plane, per the v1 contract).
- RATATOSKR_CONSUMER_ID (default "ratatoskr").
- RATATOSKR_PROVIDER_HOST (default "0.0.0.0"),
  RATATOSKR_COMBINED_PROVIDER_PORT (default 8392 — distinct from :8390/:8391 so the
  composite runs side-by-side with the standalones).
- RATATOSKR_OPFEED_PATH (optional): op-feed JSONL path; plane is derived PER request
  path (memory-call→memory, affect-call→affect, handshake→combined).
"""
from __future__ import annotations

import os
from collections.abc import Mapping

from ratatoskr.provider.affect_store import open_affect_store
from ratatoskr.provider.combined import build_combined_provider_app
from ratatoskr.provider.memory_store import open_memory_store
from ratatoskr.provider.opfeed import maybe_instrument_from_env


def build_combined_app_from_env(env: Mapping[str, str] | None = None):
    """Build the combined ASGI app from environment config (testable seam)."""
    env = os.environ if env is None else env
    key = env.get("RATATOSKR_HEIMDALL_KEY")
    if not key:
        raise RuntimeError(
            "RATATOSKR_HEIMDALL_KEY is required to serve the combined provider"
        )
    raw_dim = env.get("RATATOSKR_MEMORY_EMBEDDING_DIM")
    if not raw_dim:
        raise RuntimeError(
            "RATATOSKR_MEMORY_EMBEDDING_DIM is required (Worldtree's PINNED_EMBEDDER_DIM)"
        )
    try:
        embedding_dim = int(raw_dim)
    except ValueError as exc:
        raise RuntimeError(
            f"RATATOSKR_MEMORY_EMBEDDING_DIM must be an int, got {raw_dim!r}"
        ) from exc
    if embedding_dim <= 0:
        raise RuntimeError("RATATOSKR_MEMORY_EMBEDDING_DIM must be a positive int")

    affect_store = open_affect_store(env.get("RATATOSKR_AFFECT_DB", "affect.db"))
    memory_store = open_memory_store(
        env.get("RATATOSKR_MEMORY_DB", "memory.db"), embedding_dim=embedding_dim
    )
    app = build_combined_provider_app(
        memory_store,
        affect_store,
        heimdall_key=key.encode(),
        consumer_id=env.get("RATATOSKR_CONSUMER_ID", "ratatoskr"),
    )
    # Issue #17 (Observe): opt-in dispatch-layer op-feed; plane='combined' derives the
    # OpEvent plane per request path (INV-012).
    return maybe_instrument_from_env(app, env, plane="combined")


def main() -> None:
    import uvicorn

    uvicorn.run(
        build_combined_app_from_env(),
        host=os.environ.get("RATATOSKR_PROVIDER_HOST", "0.0.0.0"),
        port=int(os.environ.get("RATATOSKR_COMBINED_PROVIDER_PORT", "8392")),
    )
