"""HarnessLog：SQLite 薄包装 + RunLogImpl 只读查询端口。

HarnessLog 提供统一的结构化日志接口，从 TRM4 直搬，保留全部线程安全与幂等语义。
同 run_id 重复创建时通过 ON CONFLICT DO UPDATE 更新 started_at/config/status。
RunLogImpl 实现 core/evolution/protocols.py::RunLog Protocol，用独立连接做只读 SELECT，
不经 HarnessLog 生命周期（不触发 _runs upsert），避免污染运行状态。
"""

from __future__ import annotations

import asyncio
import json
import sqlite3
import subprocess
import threading
from datetime import UTC, datetime
from pathlib import Path
from typing import Any


def _get_git_sha() -> str | None:
    """获取当前 git commit SHA。"""
    try:
        result = subprocess.run(
            ["git", "rev-parse", "HEAD"],
            capture_output=True,
            text=True,
            check=True,
        )
        return result.stdout.strip()
    except (subprocess.CalledProcessError, FileNotFoundError):
        return None


def _now_iso() -> str:
    """返回当前 UTC 时间的 ISO 格式字符串。"""
    return datetime.now(UTC).isoformat()


class HarnessLog:
    """SQLite 薄包装，为科研项目提供统一的结构化日志接口。

    关键设计：
    - WAL 模式 + threading.Lock 保证共享连接下并发安全。
    - ON CONFLICT DO UPDATE 保证幂等（同 run_id 多次创建不报错）并更新 started_at/config/status。
    - query 也持锁：共享连接（check_same_thread=False）下并发 SELECT + INSERT
      在同一连接上 execute 会损坏游标状态，故读也须串行化。
    - context manager 语义：正常退出 completed，异常退出 failed。

    参数:
        db_path: SQLite 数据库文件路径。
        run_id: 本次运行的唯一标识。
        git_sha: 代码版本，默认自动获取。
        config_snapshot: 本次运行的配置快照。
    """

    def __init__(
        self,
        db_path: str,
        run_id: str,
        git_sha: str | None = None,
        config_snapshot: dict[str, Any] | None = None,
    ) -> None:
        self._run_id = run_id
        Path(db_path).parent.mkdir(parents=True, exist_ok=True)
        self._conn = sqlite3.connect(db_path, check_same_thread=False)
        self._lock = threading.Lock()
        self._conn.row_factory = sqlite3.Row
        self._conn.execute("PRAGMA journal_mode=WAL")
        self._init_fixed_tables()
        resolved_sha = git_sha or _get_git_sha()
        config_json = json.dumps(config_snapshot, ensure_ascii=False) if config_snapshot else None
        self._conn.execute(
            "INSERT INTO _runs"
            " (run_id, git_sha, started_at, config, status)"
            " VALUES (?, ?, ?, ?, ?)"
            " ON CONFLICT(run_id) DO UPDATE SET"
            " started_at=excluded.started_at,"
            " config=excluded.config,"
            " status=excluded.status",
            (run_id, resolved_sha, _now_iso(), config_json, "running"),
        )
        self._conn.commit()

    def _init_fixed_tables(self) -> None:
        """创建 _runs 和 _events 固定表。"""
        self._conn.execute("""
            CREATE TABLE IF NOT EXISTS _runs (
                run_id TEXT PRIMARY KEY,
                git_sha TEXT,
                started_at TEXT,
                finished_at TEXT,
                config JSON,
                status TEXT DEFAULT 'running',
                skills_version TEXT,
                prompts_version TEXT,
                questions_ref TEXT
            )
        """)
        self._conn.execute("""
            CREATE TABLE IF NOT EXISTS _events (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                run_id TEXT,
                timestamp TEXT,
                event_type TEXT,
                payload JSON
            )
        """)
        self._conn.commit()

    def create_table(
        self,
        name: str,
        columns: dict[str, str],
        primary_key: str | None = None,
    ) -> None:
        """创建自定义表，自动追加 run_id 和 timestamp 列。

        参数:
            name: 表名。
            columns: 列定义，如 {"epoch": "INTEGER", "loss": "REAL"}。
            primary_key: 主键列名。
        """
        all_columns = {"run_id": "TEXT", "timestamp": "TEXT"}
        all_columns.update(columns)
        col_defs = []
        for col_name, col_type in all_columns.items():
            pk_suffix = " PRIMARY KEY" if col_name == primary_key else ""
            col_defs.append(f"{col_name} {col_type}{pk_suffix}")
        sql = f"CREATE TABLE IF NOT EXISTS {name} ({', '.join(col_defs)})"
        self._conn.execute(sql)
        self._conn.commit()

    def insert(self, table: str, record: dict[str, Any], mode: str = "append") -> None:
        """插入一条记录，自动填充 run_id 和 timestamp。

        参数:
            table: 目标表名。
            record: 要插入的数据。
            mode: "append" 或 "upsert"。
        """
        enriched = {"run_id": self._run_id, "timestamp": _now_iso()}
        enriched.update(record)
        cols = list(enriched.keys())
        placeholders = ", ".join(["?"] * len(cols))
        col_names = ", ".join(cols)
        values = [enriched[c] for c in cols]
        if mode == "upsert":
            sql = f"INSERT OR REPLACE INTO {table} ({col_names}) VALUES ({placeholders})"
        else:
            sql = f"INSERT INTO {table} ({col_names}) VALUES ({placeholders})"
        with self._lock:
            self._conn.execute(sql, values)
            self._conn.commit()

    def insert_many(self, table: str, records: list[dict[str, Any]], mode: str = "append") -> None:
        """批量插入多条记录。

        参数:
            table: 目标表名。
            records: 要插入的数据列表。
            mode: "append" 或 "upsert"。
        """
        for record in records:
            self.insert(table, record, mode=mode)

    def execute(self, sql: str, params: tuple[Any, ...] = ()) -> None:
        """执行原生 SQL 写操作。

        参数:
            sql: SQL 语句。
            params: 参数元组。
        """
        with self._lock:
            self._conn.execute(sql, params)
            self._conn.commit()

    def query(self, sql: str, params: tuple[Any, ...] = ()) -> list[dict[str, Any]]:
        """执行原生 SQL 查询，返回 list[dict]。

        与所有写方法同持 self._lock：共享连接（check_same_thread=False）下，
        并发 SELECT 与 INSERT 在同一连接上 execute 会损坏游标状态，故读也须串行化。

        参数:
            sql: SQL 查询语句。
            params: 参数元组。

        返回:
            查询结果列表，每行为一个字典。
        """
        with self._lock:
            cursor = self._conn.execute(sql, params)
            columns = [desc[0] for desc in cursor.description]
            return [dict(zip(columns, row, strict=True)) for row in cursor.fetchall()]

    def log_event(self, event_type: str, payload: dict[str, Any]) -> None:
        """向 _events 表写入一条事件。

        参数:
            event_type: 事件类型标识。
            payload: 事件数据。
        """
        with self._lock:
            self._conn.execute(
                "INSERT INTO _events (run_id, timestamp, event_type, payload) VALUES (?, ?, ?, ?)",
                (
                    self._run_id,
                    _now_iso(),
                    event_type,
                    json.dumps(payload, ensure_ascii=False),
                ),
            )
            self._conn.commit()

    def close(self, status: str = "completed") -> None:
        """更新运行状态并关闭连接。

        参数:
            status: 最终状态，"completed" 或 "failed"。
        """
        with self._lock:
            self._conn.execute(
                "UPDATE _runs SET finished_at = ?, status = ? WHERE run_id = ?",
                (_now_iso(), status, self._run_id),
            )
            self._conn.commit()
            self._conn.close()

    def __enter__(self) -> HarnessLog:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: Any,
    ) -> None:
        status = "failed" if exc_type is not None else "completed"
        self.close(status=status)


# ---------------------------------------------------------------------------
# RunLogImpl — core/evolution/protocols.py::RunLog 的只读实现
# ---------------------------------------------------------------------------


def _read_table(
    db_path: str,
    table: str,
    run_id: str,
    *,
    question_ids: list[str] | None = None,
) -> list[dict[str, Any]]:
    """纯读某表指定 run 的行——不经 HarnessLog 生命周期，避免回读污染 _runs 运行状态。

    HarnessLog.__enter__/__exit__ 会对 run_id 做 INSERT OR IGNORE 并在退出时标 completed；
    回读指标绝不应改运行状态，故走独立只读连接（仅 SELECT）。

    参数:
        db_path: SQLite 路径。
        table: 表名（内部固定常量，非外部输入，无注入风险）。
        run_id: 过滤的 run ID。
        question_ids: 可选的 question_id 过滤列表。

    返回:
        行 dict 列表；表尚未建（没写过）视为无数据返 []。
    """
    conn = sqlite3.connect(db_path)
    conn.row_factory = sqlite3.Row
    try:
        exists = conn.execute(
            "SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table,)
        ).fetchone()
        if exists is None:
            return []

        if question_ids is not None:
            placeholders = ", ".join(["?"] * len(question_ids))
            sql = f"SELECT * FROM {table} WHERE run_id = ? AND question_id IN ({placeholders})"
            rows = conn.execute(sql, (run_id, *question_ids)).fetchall()
        else:
            rows = conn.execute(f"SELECT * FROM {table} WHERE run_id = ?", (run_id,)).fetchall()

        return [dict(r) for r in rows]
    finally:
        conn.close()


class RunLogImpl:
    """RunLog Protocol 的只读实现。

    用独立 sqlite3.connect 做 SELECT，不经 HarnessLog 生命周期（不触发 _runs INSERT），
    asyncio.to_thread 包装同步 SQL 查询，避免引入 aiosqlite 新依赖。

    参数:
        db_path: SQLite 数据库文件路径。
    """

    def __init__(self, db_path: str) -> None:
        self._db_path = db_path

    async def get_predictions(
        self,
        run_id: str,
        *,
        question_ids: list[str] | None = None,
    ) -> list[dict[str, Any]]:
        """查询指定 run 的预测记录。

        参数:
            run_id: 运行标识。
            question_ids: 可选的题目 ID 过滤列表。

        返回:
            预测记录字典列表。
        """
        return await asyncio.to_thread(
            _read_table, self._db_path, "predictions", run_id, question_ids=question_ids
        )

    async def get_traces(
        self,
        run_id: str,
        *,
        question_ids: list[str] | None = None,
    ) -> list[dict[str, Any]]:
        """查询指定 run 的推理轨迹。

        参数:
            run_id: 运行标识。
            question_ids: 可选的题目 ID 过滤列表。

        返回:
            轨迹记录字典列表。
        """
        return await asyncio.to_thread(
            _read_table, self._db_path, "traces", run_id, question_ids=question_ids
        )
