"""Per-session SQLite trace database — write layer.

Each session folder gets its own ``logs/trace.db``.  This module is called
from the agent runtime (``agent_sdk.py``) to record structured trace events.
The text log (``agent_trace.log``) continues to be written for human debugging,
but this database is the structured source of truth.
"""

from __future__ import annotations

import sqlite3
import threading
from datetime import datetime, timezone
from pathlib import Path

_SCHEMA_SQL = """\
CREATE TABLE IF NOT EXISTS trace_events (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    call_id         TEXT    NOT NULL,
    agent_name      TEXT    NOT NULL,
    event_type      TEXT    NOT NULL,
    timestamp_utc   TEXT    NOT NULL,
    model           TEXT    NOT NULL DEFAULT '',
    provider        TEXT    NOT NULL DEFAULT '',
    mode            TEXT    NOT NULL DEFAULT '',
    session_id      TEXT    NOT NULL DEFAULT '',
    depth           INTEGER NOT NULL DEFAULT 0,
    duration_secs   REAL,
    cost_usd        REAL,
    status          TEXT    NOT NULL DEFAULT '',
    error_message   TEXT    NOT NULL DEFAULT '',
    is_resume       INTEGER NOT NULL DEFAULT 0,
    created_at      TEXT    NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_trace_call_id    ON trace_events (call_id);
CREATE INDEX IF NOT EXISTS idx_trace_event_type ON trace_events (event_type);
CREATE INDEX IF NOT EXISTS idx_trace_created_at ON trace_events (created_at);
"""

_INSERT_SQL = """\
INSERT INTO trace_events (
    call_id, agent_name, event_type, timestamp_utc,
    model, provider, mode, session_id, depth,
    duration_secs, cost_usd, status, error_message,
    is_resume, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""

# Thread-local connection cache so each thread reuses one connection per db path.
_local = threading.local()


def _get_conn(db_path: Path) -> sqlite3.Connection:
    """Return a cached WAL-mode connection for *db_path*, creating the DB on first use."""
    cache: dict[str, sqlite3.Connection] = getattr(_local, "conns", None) or {}
    _local.conns = cache

    key = str(db_path)
    conn = cache.get(key)
    if conn is not None:
        return conn

    db_path.parent.mkdir(parents=True, exist_ok=True)
    conn = sqlite3.connect(str(db_path), timeout=5)
    conn.execute("PRAGMA journal_mode=WAL")
    conn.execute("PRAGMA busy_timeout=3000")
    conn.executescript(_SCHEMA_SQL)
    cache[key] = conn
    return conn


def insert_trace_event(
    session_folder: str,
    *,
    call_id: str,
    agent_name: str,
    event_type: str,
    model: str = "",
    provider: str = "",
    mode: str = "",
    session_id: str = "",
    depth: int = 0,
    duration_secs: float | None = None,
    cost_usd: float | None = None,
    status: str = "",
    error_message: str = "",
    is_resume: bool = False,
) -> int:
    """Insert a single trace event row.  Returns the new row id."""
    db_path = Path(session_folder) / "logs" / "trace.db"
    now = datetime.now(tz=timezone.utc).isoformat()
    timestamp_utc = now

    conn = _get_conn(db_path)
    cur = conn.execute(
        _INSERT_SQL,
        (
            call_id,
            agent_name,
            event_type,
            timestamp_utc,
            model,
            provider,
            mode,
            session_id,
            depth,
            duration_secs,
            cost_usd,
            status,
            error_message,
            1 if is_resume else 0,
            now,
        ),
    )
    conn.commit()
    return cur.lastrowid
