"""ConversationLog — append-only SQLite store for raw conversation capture."""

import json
import sqlite3
import time
from pathlib import Path

from neo_cortex.models import ConversationEntry, ConversationAppendRequest


class ConversationLog:
    """Append-only SQLite log. Every message, every event, nothing lost."""

    def __init__(self, db_path: str):
        Path(db_path).parent.mkdir(parents=True, exist_ok=True)
        self._conn = sqlite3.connect(db_path, check_same_thread=False)
        self._conn.row_factory = sqlite3.Row
        self._conn.execute("PRAGMA journal_mode=WAL")
        self._create_table()
        self._migrate()

    def _create_table(self):
        self._conn.execute("""
            CREATE TABLE IF NOT EXISTS conversation_log (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp REAL NOT NULL,
                session_id TEXT NOT NULL,
                role TEXT NOT NULL,
                content TEXT NOT NULL,
                event_type TEXT,
                model TEXT,
                metadata TEXT
            )
        """)
        self._conn.execute("""
            CREATE INDEX IF NOT EXISTS idx_log_session
            ON conversation_log(session_id)
        """)
        self._conn.execute("""
            CREATE INDEX IF NOT EXISTS idx_log_timestamp
            ON conversation_log(timestamp DESC)
        """)
        self._conn.commit()

    def append(self, request: ConversationAppendRequest) -> int:
        """Append a single entry. Returns the row ID."""
        ts = request.timestamp or time.time()
        meta_json = json.dumps(request.metadata) if request.metadata else None
        cursor = self._conn.execute(
            """INSERT INTO conversation_log
               (timestamp, session_id, role, content, event_type, model, metadata)
               VALUES (?, ?, ?, ?, ?, ?, ?)""",
            (ts, request.session_id, request.role, request.content,
             request.event_type, request.model, meta_json),
        )
        self._conn.commit()
        return cursor.lastrowid

    def get_session(self, session_id: str) -> list[ConversationEntry]:
        """Get all entries for a session, ordered by timestamp."""
        rows = self._conn.execute(
            """SELECT * FROM conversation_log
               WHERE session_id = ?
               ORDER BY timestamp ASC""",
            (session_id,),
        ).fetchall()
        return [self._row_to_entry(r) for r in rows]

    def get_recent(self, n: int = 50) -> list[ConversationEntry]:
        """Get the N most recent entries across all sessions."""
        rows = self._conn.execute(
            """SELECT * FROM conversation_log
               ORDER BY timestamp DESC LIMIT ?""",
            (n,),
        ).fetchall()
        return [self._row_to_entry(r) for r in reversed(rows)]

    def get_sessions(self) -> list[dict]:
        """List all sessions with entry counts."""
        rows = self._conn.execute(
            """SELECT session_id, COUNT(*) as entries,
                      MIN(timestamp) as first_ts, MAX(timestamp) as last_ts
               FROM conversation_log
               GROUP BY session_id
               ORDER BY last_ts DESC"""
        ).fetchall()
        return [dict(r) for r in rows]

    def count(self) -> int:
        row = self._conn.execute("SELECT COUNT(*) FROM conversation_log").fetchone()
        return row[0]

    def get_undigested(self, limit: int = 500) -> list[ConversationEntry]:
        """Get entries not yet processed by the digestor."""
        rows = self._conn.execute(
            "SELECT * FROM conversation_log WHERE digested = 0 ORDER BY timestamp ASC LIMIT ?",
            (limit,),
        ).fetchall()
        return [self._row_to_entry(r) for r in rows]

    def get_undigested_by_session(self, limit: int = 500) -> dict[str, list[ConversationEntry]]:
        """Get undigested entries grouped by session_id."""
        entries = self.get_undigested(limit)
        grouped: dict[str, list[ConversationEntry]] = {}
        for e in entries:
            grouped.setdefault(e.session_id, []).append(e)
        return grouped

    def mark_digested(self, ids: list[int]) -> None:
        """Mark entries as processed by the digestor."""
        if not ids:
            return
        placeholders = ",".join("?" for _ in ids)
        self._conn.execute(
            f"UPDATE conversation_log SET digested = 1 WHERE id IN ({placeholders})", ids
        )
        self._conn.commit()

    def reset_digested(self) -> int:
        """Mark ALL entries as undigested. Returns count of reset entries."""
        cur = self._conn.execute("UPDATE conversation_log SET digested = 0 WHERE digested = 1")
        self._conn.commit()
        return cur.rowcount

    def _migrate(self):
        """Add digested column if not present (for existing DBs)."""
        cols = {c[1] for c in self._conn.execute("PRAGMA table_info(conversation_log)").fetchall()}
        if "digested" not in cols:
            self._conn.execute(
                "ALTER TABLE conversation_log ADD COLUMN digested INTEGER DEFAULT 0"
            )
            self._conn.commit()

    @staticmethod
    def _parse_timestamp(val) -> float:
        """Convert timestamp to float. Handles both float and ISO 8601 strings."""
        if isinstance(val, (int, float)):
            return float(val)
        try:
            return float(val)
        except (ValueError, TypeError):
            pass
        # ISO 8601: 2026-02-22T10:00:44.358Z
        from datetime import datetime, timezone
        try:
            dt = datetime.fromisoformat(val.replace("Z", "+00:00"))
            return dt.timestamp()
        except Exception:
            return time.time()

    def _row_to_entry(self, row: sqlite3.Row) -> ConversationEntry:
        meta = json.loads(row["metadata"]) if row["metadata"] else None
        return ConversationEntry(
            id=row["id"],
            timestamp=self._parse_timestamp(row["timestamp"]),
            session_id=row["session_id"],
            role=row["role"],
            content=row["content"],
            event_type=row["event_type"],
            model=row["model"],
            metadata=meta,
        )
