/**
 * Session persistence — SQLite-backed agent session & message storage.
 * Uses bun:sqlite. DB: ~/.config/agor/settings.db (shared with SettingsDb).
 *
 * Tables: agent_sessions, agent_messages
 */

import { Database } from "bun:sqlite";
import { homedir } from "os";
import { join } from "path";
import { openDb } from "./db-utils.ts";

// ── DB path ──────────────────────────────────────────────────────────────────

const CONFIG_DIR = join(homedir(), ".config", "agor");
const DB_PATH = join(CONFIG_DIR, "settings.db");

// ── Types ────────────────────────────────────────────────────────────────────

export interface StoredSession {
  projectId: string;
  sessionId: string;
  provider: string;
  status: string;
  costUsd: number;
  inputTokens: number;
  outputTokens: number;
  model: string;
  error?: string;
  createdAt: number;
  updatedAt: number;
}

export interface StoredMessage {
  sessionId: string;
  msgId: string;
  role: string;
  content: string;
  toolName?: string;
  toolInput?: string;
  timestamp: number;
  costUsd: number;
  inputTokens: number;
  outputTokens: number;
}

// ── Schema ───────────────────────────────────────────────────────────────────

const SESSION_SCHEMA = `
CREATE TABLE IF NOT EXISTS agent_sessions (
  project_id   TEXT NOT NULL,
  session_id   TEXT PRIMARY KEY,
  provider     TEXT NOT NULL,
  status       TEXT NOT NULL DEFAULT 'idle',
  cost_usd     REAL NOT NULL DEFAULT 0,
  input_tokens INTEGER NOT NULL DEFAULT 0,
  output_tokens INTEGER NOT NULL DEFAULT 0,
  model        TEXT NOT NULL DEFAULT '',
  error        TEXT,
  created_at   INTEGER NOT NULL,
  updated_at   INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_agent_sessions_project
  ON agent_sessions(project_id);

CREATE TABLE IF NOT EXISTS agent_messages (
  session_id    TEXT NOT NULL,
  msg_id        TEXT NOT NULL,
  role          TEXT NOT NULL,
  content       TEXT NOT NULL DEFAULT '',
  tool_name     TEXT,
  tool_input    TEXT,
  timestamp     INTEGER NOT NULL,
  cost_usd      REAL NOT NULL DEFAULT 0,
  input_tokens  INTEGER NOT NULL DEFAULT 0,
  output_tokens INTEGER NOT NULL DEFAULT 0,
  PRIMARY KEY (session_id, msg_id),
  FOREIGN KEY (session_id) REFERENCES agent_sessions(session_id) ON DELETE CASCADE
);

CREATE INDEX IF NOT EXISTS idx_agent_messages_session
  ON agent_messages(session_id, timestamp);
`;

// ── SessionDb class ──────────────────────────────────────────────────────────

export class SessionDb {
  private db: Database;

  constructor() {
    this.db = openDb(DB_PATH, { foreignKeys: true });
    this.db.exec(SESSION_SCHEMA);
  }

  // ── Sessions ─────────────────────────────────────────────────────────────

  saveSession(s: StoredSession): void {
    this.db
      .query(
        `INSERT INTO agent_sessions
           (project_id, session_id, provider, status, cost_usd,
            input_tokens, output_tokens, model, error, created_at, updated_at)
         VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)
         ON CONFLICT(session_id) DO UPDATE SET
           status = excluded.status,
           cost_usd = excluded.cost_usd,
           input_tokens = excluded.input_tokens,
           output_tokens = excluded.output_tokens,
           model = excluded.model,
           error = excluded.error,
           updated_at = excluded.updated_at`
      )
      .run(
        s.projectId,
        s.sessionId,
        s.provider,
        s.status,
        s.costUsd,
        s.inputTokens,
        s.outputTokens,
        s.model,
        s.error ?? null,
        s.createdAt,
        s.updatedAt
      );
  }

  loadSession(projectId: string): StoredSession | null {
    const row = this.db
      .query<
        {
          project_id: string;
          session_id: string;
          provider: string;
          status: string;
          cost_usd: number;
          input_tokens: number;
          output_tokens: number;
          model: string;
          error: string | null;
          created_at: number;
          updated_at: number;
        },
        [string]
      >(
        `SELECT * FROM agent_sessions
         WHERE project_id = ?
         ORDER BY updated_at DESC LIMIT 1`
      )
      .get(projectId);

    if (!row) return null;
    return {
      projectId: row.project_id,
      sessionId: row.session_id,
      provider: row.provider,
      status: row.status,
      costUsd: row.cost_usd,
      inputTokens: row.input_tokens,
      outputTokens: row.output_tokens,
      model: row.model,
      error: row.error ?? undefined,
      createdAt: row.created_at,
      updatedAt: row.updated_at,
    };
  }

  listSessionsByProject(projectId: string): StoredSession[] {
    const rows = this.db
      .query<
        {
          project_id: string;
          session_id: string;
          provider: string;
          status: string;
          cost_usd: number;
          input_tokens: number;
          output_tokens: number;
          model: string;
          error: string | null;
          created_at: number;
          updated_at: number;
        },
        [string]
      >(
        `SELECT * FROM agent_sessions
         WHERE project_id = ?
         ORDER BY updated_at DESC LIMIT 20`
      )
      .all(projectId);

    return rows.map((r) => ({
      projectId: r.project_id,
      sessionId: r.session_id,
      provider: r.provider,
      status: r.status,
      costUsd: r.cost_usd,
      inputTokens: r.input_tokens,
      outputTokens: r.output_tokens,
      model: r.model,
      error: r.error ?? undefined,
      createdAt: r.created_at,
      updatedAt: r.updated_at,
    }));
  }

  // ── Messages ─────────────────────────────────────────────────────────────

  saveMessage(m: StoredMessage): void {
    this.db
      .query(
        `INSERT INTO agent_messages
           (session_id, msg_id, role, content, tool_name, tool_input,
            timestamp, cost_usd, input_tokens, output_tokens)
         VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
         ON CONFLICT(session_id, msg_id) DO NOTHING`
      )
      .run(
        m.sessionId,
        m.msgId,
        m.role,
        m.content,
        m.toolName ?? null,
        m.toolInput ?? null,
        m.timestamp,
        m.costUsd,
        m.inputTokens,
        m.outputTokens
      );
  }

  saveMessages(msgs: StoredMessage[]): void {
    const stmt = this.db.prepare(
      `INSERT INTO agent_messages
         (session_id, msg_id, role, content, tool_name, tool_input,
          timestamp, cost_usd, input_tokens, output_tokens)
       VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
       ON CONFLICT(session_id, msg_id) DO NOTHING`
    );

    const tx = this.db.transaction(() => {
      for (const m of msgs) {
        stmt.run(
          m.sessionId,
          m.msgId,
          m.role,
          m.content,
          m.toolName ?? null,
          m.toolInput ?? null,
          m.timestamp,
          m.costUsd,
          m.inputTokens,
          m.outputTokens
        );
      }
    });
    tx();
  }

  loadMessages(sessionId: string): StoredMessage[] {
    const rows = this.db
      .query<
        {
          session_id: string;
          msg_id: string;
          role: string;
          content: string;
          tool_name: string | null;
          tool_input: string | null;
          timestamp: number;
          cost_usd: number;
          input_tokens: number;
          output_tokens: number;
        },
        [string]
      >(
        `SELECT * FROM agent_messages
         WHERE session_id = ?
         ORDER BY timestamp ASC`
      )
      .all(sessionId);

    return rows.map((r) => ({
      sessionId: r.session_id,
      msgId: r.msg_id,
      role: r.role,
      content: r.content,
      toolName: r.tool_name ?? undefined,
      toolInput: r.tool_input ?? undefined,
      timestamp: r.timestamp,
      costUsd: r.cost_usd,
      inputTokens: r.input_tokens,
      outputTokens: r.output_tokens,
    }));
  }

  // ── Cleanup ──────────────────────────────────────────────────────────────

  /** Delete sessions older than maxAgeDays for a project, keeping at most keepCount. */
  pruneOldSessions(projectId: string, keepCount = 10): void {
    this.db
      .query(
        `DELETE FROM agent_sessions
         WHERE project_id = ?1
           AND session_id NOT IN (
             SELECT session_id FROM agent_sessions
             WHERE project_id = ?1
             ORDER BY updated_at DESC
             LIMIT ?2
           )`
      )
      .run(projectId, keepCount);
  }

  close(): void {
    this.db.close();
  }
}

// Singleton
export const sessionDb = new SessionDb();
