import Database from 'better-sqlite3';
import type { Message } from '../models/types.js';
import type { PairingStore, ApprovedSender } from '../channels/pairing.js';
import type { HistoryMetadata } from './indexer.js';

/** Parse a duration string like '30d', '7d', '12h' to milliseconds. Returns null if invalid or '0'. */
export function parseDuration(s: string): number | null {
  if (s === '0' || s === 'false') {return null;}
  const match = s.match(/^(\d+)(h|d)$/);
  if (!match) {return null;}
  const [, n, unit] = match;
  return unit === 'h' ? Number(n) * 3600_000 : Number(n) * 86_400_000;
}

export class SessionStore {
  private db: Database.Database;

  constructor(dbPath: string) {
    this.db = new Database(dbPath);
    this.init();
  }

  private init(): void {
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS messages (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        session_id TEXT NOT NULL,
        role TEXT NOT NULL,
        content TEXT NOT NULL,
        created_at INTEGER NOT NULL DEFAULT (unixepoch())
      );
      CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);
      CREATE TABLE IF NOT EXISTS pairing_approved (
        channel TEXT NOT NULL,
        sender_id TEXT NOT NULL,
        approved_at INTEGER NOT NULL,
        code_used TEXT NOT NULL,
        PRIMARY KEY (channel, sender_id)
      );
      CREATE TABLE IF NOT EXISTS session_config (
        session_id TEXT NOT NULL,
        key TEXT NOT NULL,
        value TEXT NOT NULL,
        PRIMARY KEY (session_id, key)
      );
      CREATE INDEX IF NOT EXISTS idx_session_config_session ON session_config(session_id);
    `);

    const messageColumns = this.db.prepare('PRAGMA table_info(messages)').all() as Array<{ name: string }>;
    if (!messageColumns.some(column => column.name === 'metadata')) {
      this.db.exec('ALTER TABLE messages ADD COLUMN metadata TEXT');
    }
  }

  addMessage(sessionId: string, message: Message, metadata?: HistoryMetadata): void {
    const stmt = this.db.prepare(
      'INSERT INTO messages (session_id, role, content, metadata) VALUES (?, ?, ?, ?)',
    );
    stmt.run(sessionId, message.role, message.content, metadata ? JSON.stringify(metadata) : null);
  }

  getMessages(sessionId: string): Message[] {
    const stmt = this.db.prepare(
      'SELECT role, content FROM messages WHERE session_id = ? ORDER BY id ASC',
    );
    const rows = stmt.all(sessionId) as Array<{ role: string; content: string }>;
    return rows.map(row => ({
      role: row.role as 'user' | 'assistant',
      content: row.content,
    }));
  }

  /**
   * Atomically replace all messages for a session.
   * Used by compaction to swap full history with a compacted version.
   * Runs in a transaction: delete all → re-insert in order.
   */
  replaceMessages(sessionId: string, messages: Message[]): void {
    const transaction = this.db.transaction(() => {
      // Delete existing messages
      this.db.prepare('DELETE FROM messages WHERE session_id = ?').run(sessionId);
      // Re-insert in order
      const insert = this.db.prepare(
        'INSERT INTO messages (session_id, role, content, metadata) VALUES (?, ?, ?, ?)',
      );
      for (const msg of messages) {
        insert.run(sessionId, msg.role, msg.content, null);
      }
    });
    transaction();
  }

  clearSession(sessionId: string): void {
    const transaction = this.db.transaction(() => {
      this.db.prepare('DELETE FROM messages WHERE session_id = ?').run(sessionId);
      this.db.prepare('DELETE FROM session_config WHERE session_id = ?').run(sessionId);
    });
    transaction();
  }

  listSessions(): string[] {
    const stmt = this.db.prepare('SELECT DISTINCT session_id FROM messages');
    const rows = stmt.all() as Array<{ session_id: string }>;
    return rows.map(row => row.session_id);
  }

  /** Delete all messages for sessions with no activity since the given timestamp. Returns pruned session IDs. */
  pruneStale(beforeTimestamp: number): string[] {
    const stale = this.db.prepare(`
      SELECT session_id FROM messages
      GROUP BY session_id
      HAVING MAX(created_at) < ?
    `).all(beforeTimestamp) as Array<{ session_id: string }>;

    if (stale.length === 0) {return [];}

    const deleteMessages = this.db.prepare('DELETE FROM messages WHERE session_id = ?');
    const deleteConfig = this.db.prepare('DELETE FROM session_config WHERE session_id = ?');
    const transaction = this.db.transaction(() => {
      for (const { session_id } of stale) {
        deleteMessages.run(session_id);
        deleteConfig.run(session_id);
      }
    });
    transaction();

    return stale.map(r => r.session_id);
  }

  getPairingStore(): PairingStore {
    return {
      loadApproved: (): ApprovedSender[] => {
        const rows = this.db.prepare(
          'SELECT channel, sender_id, approved_at, code_used FROM pairing_approved',
        ).all() as Array<{ channel: string; sender_id: string; approved_at: number; code_used: string }>;
        return rows.map(r => ({
          channel: r.channel,
          senderId: r.sender_id,
          approvedAt: r.approved_at,
          codeUsed: r.code_used,
        }));
      },
      saveApproved: (sender: ApprovedSender): void => {
        this.db.prepare(`
          INSERT OR REPLACE INTO pairing_approved (channel, sender_id, approved_at, code_used)
          VALUES (?, ?, ?, ?)
        `).run(sender.channel, sender.senderId, sender.approvedAt, sender.codeUsed);
      },
      removeApproved: (channel: string, senderId: string): void => {
        this.db.prepare(
          'DELETE FROM pairing_approved WHERE channel = ? AND sender_id = ?',
        ).run(channel, senderId);
      },
    };
  }

  /** Get a single config value for a session. */
  getSessionConfig(sessionId: string, key: string): string | undefined {
    const stmt = this.db.prepare(
      'SELECT value FROM session_config WHERE session_id = ? AND key = ?',
    );
    const row = stmt.get(sessionId, key) as { value: string } | undefined;
    return row?.value;
  }

  /** Get all config values for a session. */
  getAllSessionConfig(sessionId: string): Record<string, string> {
    const stmt = this.db.prepare(
      'SELECT key, value FROM session_config WHERE session_id = ?',
    );
    const rows = stmt.all(sessionId) as Array<{ key: string; value: string }>;
    const result: Record<string, string> = {};
    for (const row of rows) {
      result[row.key] = row.value;
    }
    return result;
  }

  /** Set a config value for a session. Upserts (INSERT OR REPLACE). */
  setSessionConfig(sessionId: string, key: string, value: string): void {
    this.db.prepare(
      'INSERT OR REPLACE INTO session_config (session_id, key, value) VALUES (?, ?, ?)',
    ).run(sessionId, key, value);
  }

  /** Delete a config value for a session. */
  deleteSessionConfig(sessionId: string, key: string): void {
    this.db.prepare(
      'DELETE FROM session_config WHERE session_id = ? AND key = ?',
    ).run(sessionId, key);
  }

  /** Delete all config for a session (used when session is cleared/pruned). */
  clearSessionConfig(sessionId: string): void {
    this.db.prepare(
      'DELETE FROM session_config WHERE session_id = ?',
    ).run(sessionId);
  }

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

  getMessagesWithMetadata(sessionId: string): Array<{
    id: number;
    sessionId: string;
    role: 'user' | 'assistant';
    content: string;
    createdAt: number;
    metadata: HistoryMetadata | null;
  }> {
    const stmt = this.db.prepare(
      'SELECT id, session_id, role, content, created_at, metadata FROM messages WHERE session_id = ? ORDER BY id ASC',
    );
    const rows = stmt.all(sessionId) as Array<{
      id: number;
      session_id: string;
      role: string;
      content: string;
      created_at: number;
      metadata: string | null;
    }>;

    return rows.map(row => ({
      id: row.id,
      sessionId: row.session_id,
      role: row.role as 'user' | 'assistant',
      content: row.content,
      createdAt: row.created_at,
      metadata: row.metadata ? JSON.parse(row.metadata) as HistoryMetadata : null,
    }));
  }

  getAllMessagesWithMetadata(): Array<{
    id: number;
    sessionId: string;
    role: 'user' | 'assistant';
    content: string;
    createdAt: number;
    metadata: HistoryMetadata | null;
  }> {
    const stmt = this.db.prepare(
      'SELECT id, session_id, role, content, created_at, metadata FROM messages ORDER BY id ASC',
    );
    const rows = stmt.all() as Array<{
      id: number;
      session_id: string;
      role: string;
      content: string;
      created_at: number;
      metadata: string | null;
    }>;

    return rows.map(row => ({
      id: row.id,
      sessionId: row.session_id,
      role: row.role as 'user' | 'assistant',
      content: row.content,
      createdAt: row.created_at,
      metadata: row.metadata ? JSON.parse(row.metadata) as HistoryMetadata : null,
    }));
  }

  updateMessageMetadata(messageId: number, metadata: HistoryMetadata): void {
    this.db.prepare('UPDATE messages SET metadata = ? WHERE id = ?').run(JSON.stringify(metadata), messageId);
  }
}
