"""SelfModel CRUD against ``selfmodel_records`` + ``selfmodel_capability_claims``.

All writes emit audit events:

- ``selfmodel_snapshot_landed`` on ``store_snapshot``.
- ``selfmodel_capability_claim_added`` once per claim attached to a
  newly-stored SelfModel (one event per claim).

State transitions (``selfmodel_falsified`` /
``selfmodel_marked_stale``) live in :mod:`arborist.selfmodel.falsify`.

Audit events use ``arborist.store.append_audit`` so they chain through
the existing ``audit_events`` table; no separate chain.
"""

from __future__ import annotations

import sqlite3
import time
from typing import Iterable, Optional

from arborist.selfmodel.canonical import (
    CapabilityClaim,
    SelfModel,
    canonical_body,
    canonical_claim,
    claim_hash,
    selfmodel_root,
)
from arborist.store import append_audit


def store_snapshot(
    conn: sqlite3.Connection,
    model: SelfModel,
    claims: Iterable[CapabilityClaim] = (),
    *,
    ts: Optional[int] = None,
) -> str:
    """Persist a SelfModel + its capability claims; return selfmodel_root.

    Idempotent on ``(selfmodel_root)`` — re-storing the same model is
    a no-op (no duplicate audit event). Claims are inserted with
    ``INSERT OR IGNORE`` so the same claim_hash across multiple
    SelfModels is shared by content-addressing.
    """
    root = selfmodel_root(model)
    body_blob = canonical_body(model).encode("utf-8", errors="surrogatepass")
    if ts is None:
        ts = int(time.time())

    existing = conn.execute(
        "SELECT 1 FROM selfmodel_records WHERE selfmodel_root = ?",
        (root,),
    ).fetchone()
    if existing is not None:
        return root

    event_hash = append_audit(
        conn,
        event_type="selfmodel_snapshot_landed",
        subject_root=root,
        body={
            "selfmodel_root": root,
            "schema_version": model.schema_version,
            "parent_selfmodel_root": model.parent_selfmodel_root,
            "claim_count": len(model.capability_claim_hashes),
        },
        ts=ts,
    )

    conn.execute(
        "INSERT INTO selfmodel_records ("
        " selfmodel_root, schema_version, parent_selfmodel_root,"
        " model_profile_hash, verifier_method_root, governance_policy_hash,"
        " canonicalization_version, chunking_version,"
        " accepted_patch_root, rejected_patch_root, memory_root,"
        " state, body_blob, audit_event_hash, created_at"
        ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'live', ?, ?, ?)",
        (
            root,
            model.schema_version,
            model.parent_selfmodel_root,
            model.model_profile_hash,
            model.verifier_method_root,
            model.governance_policy_hash,
            model.canonicalization_version,
            model.chunking_version,
            model.accepted_patch_root,
            model.rejected_patch_root,
            model.memory_root,
            body_blob,
            event_hash,
            ts,
        ),
    )

    for claim in claims:
        c_hash = claim_hash(claim)
        c_blob = canonical_claim(claim).encode(
            "utf-8", errors="surrogatepass"
        )
        # Inserting same claim hash from multiple selfmodels: content-
        # addressed, so OR IGNORE is correct.
        conn.execute(
            "INSERT OR IGNORE INTO selfmodel_capability_claims ("
            " claim_hash, selfmodel_root, metric, threshold, eval_digest,"
            " measured_value, measured_at, validity_horizon, body_blob"
            ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
            (
                c_hash,
                root,
                claim.metric,
                float(claim.threshold),
                claim.eval_digest,
                None
                if claim.measured_value is None
                else float(claim.measured_value),
                claim.measured_at,
                claim.validity_horizon,
                c_blob,
            ),
        )
        append_audit(
            conn,
            event_type="selfmodel_capability_claim_added",
            subject_root=root,
            body={
                "selfmodel_root": root,
                "claim_hash": c_hash,
                "metric": claim.metric,
                "threshold": float(claim.threshold),
                "eval_digest": claim.eval_digest,
            },
            ts=ts,
        )

    return root


def load(conn: sqlite3.Connection, root: str) -> Optional[dict]:
    """Read a SelfModel row by ``selfmodel_root``; return dict or None."""
    row = conn.execute(
        "SELECT selfmodel_root, schema_version, parent_selfmodel_root,"
        "       model_profile_hash, verifier_method_root,"
        "       governance_policy_hash, canonicalization_version,"
        "       chunking_version, accepted_patch_root,"
        "       rejected_patch_root, memory_root, state, body_blob,"
        "       audit_event_hash, created_at, falsified_at,"
        "       falsified_reason"
        " FROM selfmodel_records WHERE selfmodel_root = ?",
        (root,),
    ).fetchone()
    if row is None:
        return None
    return dict(row)


def latest(conn: sqlite3.Connection) -> Optional[dict]:
    """Return the most-recently-created live SelfModel, or None."""
    row = conn.execute(
        "SELECT selfmodel_root, schema_version, parent_selfmodel_root,"
        "       model_profile_hash, verifier_method_root,"
        "       governance_policy_hash, canonicalization_version,"
        "       chunking_version, accepted_patch_root,"
        "       rejected_patch_root, memory_root, state, body_blob,"
        "       audit_event_hash, created_at, falsified_at,"
        "       falsified_reason"
        " FROM selfmodel_records "
        " WHERE state = 'live' "
        " ORDER BY created_at DESC LIMIT 1"
    ).fetchone()
    if row is None:
        return None
    return dict(row)


def claims_for(conn: sqlite3.Connection, root: str) -> list[dict]:
    """Return the capability-claim rows attached to a SelfModel."""
    rows = conn.execute(
        "SELECT claim_hash, selfmodel_root, metric, threshold,"
        "       eval_digest, measured_value, measured_at,"
        "       validity_horizon, body_blob"
        " FROM selfmodel_capability_claims"
        " WHERE selfmodel_root = ?"
        " ORDER BY claim_hash",
        (root,),
    ).fetchall()
    return [dict(r) for r in rows]
