//! SQL schema for the agent ledger.
//!
//! Constructor Pattern: one cube = schema DDL + migration runner.
//! Single source of truth for table shape. Any structural change MUST
//! bump the migration list below; existing rows are preserved.

use rusqlite::{Connection, Result};

/// Maximum length (chars) accepted for `branch` and `parent_branch` columns.
/// Enforced by SQL CHECK (v3 migration) and CLI `value_parser` length cap.
pub const MAX_BRANCH_LEN: usize = 256;

/// Ordered migrations. Index = schema version. Never reorder; append only.
pub const MIGRATIONS: &[&str] = &[
    // v1 — initial schema (RULE 0.12, 2026-04-21)
    "CREATE TABLE IF NOT EXISTS agents (
        id TEXT PRIMARY KEY,
        branch TEXT NOT NULL,
        parent_branch TEXT,
        spec_sha TEXT NOT NULL,
        status TEXT NOT NULL CHECK (status IN ('running','done','failed','merged','rejected')),
        started_ts INTEGER NOT NULL,
        finished_ts INTEGER,
        summary TEXT,
        worktree_path TEXT
    );
    CREATE INDEX IF NOT EXISTS idx_parent ON agents(parent_branch);
    CREATE INDEX IF NOT EXISTS idx_status ON agents(status);",
    // v2 — Layer G DNA identity column + prefix index (2026-04-23)
    "ALTER TABLE agents ADD COLUMN dna TEXT;
    CREATE INDEX IF NOT EXISTS idx_agents_dna_prefix ON agents(substr(dna, 1, 30));",
    // v3 — length caps on branch/parent_branch (audit L1, 2026-04-23)
    // Enforced via trigger rather than table CHECK because CHECK cannot be
    // added retroactively to an existing table without rebuilding it. The
    // triggers refuse inserts / updates with over-long values.
    "CREATE TRIGGER IF NOT EXISTS trg_agents_branch_len_ins
     BEFORE INSERT ON agents
     BEGIN
        SELECT RAISE(ABORT, 'branch length exceeds 256')
            WHERE length(NEW.branch) > 256;
        SELECT RAISE(ABORT, 'parent_branch length exceeds 256')
            WHERE NEW.parent_branch IS NOT NULL AND length(NEW.parent_branch) > 256;
     END;
     CREATE TRIGGER IF NOT EXISTS trg_agents_branch_len_upd
     BEFORE UPDATE OF branch, parent_branch ON agents
     BEGIN
        SELECT RAISE(ABORT, 'branch length exceeds 256')
            WHERE length(NEW.branch) > 256;
        SELECT RAISE(ABORT, 'parent_branch length exceeds 256')
            WHERE NEW.parent_branch IS NOT NULL AND length(NEW.parent_branch) > 256;
     END;",
    // v4 — creator_id + fork_parent_id lineage columns (RULE 0.12 extension,
    // 2026-04-23). Both nullable for backward-compat with pre-v4 rows.
    // creator_id: DNA or human id of agent that spawned this fork.
    // fork_parent_id: DNA of forked-from agent if forked; NULL otherwise.
    "ALTER TABLE agents ADD COLUMN creator_id TEXT;
    ALTER TABLE agents ADD COLUMN fork_parent_id TEXT;
    CREATE INDEX IF NOT EXISTS idx_agents_creator ON agents(creator_id);
    CREATE INDEX IF NOT EXISTS idx_agents_fork_parent ON agents(fork_parent_id);",
];

/// Apply all pending migrations atomically (one transaction per version).
///
/// Prior design ran `execute_batch` and bumped `user_version` in a separate
/// call — a partial failure left the schema half-applied and wedged restarts.
/// Now each version's DDL + the `user_version` bump share a transaction, so
/// any error rolls everything back and the next startup retries cleanly.
pub fn migrate(conn: &Connection) -> Result<()> {
    let current: i64 = conn
        .query_row("PRAGMA user_version", [], |r| r.get(0))
        .unwrap_or(0);
    for (i, sql) in MIGRATIONS.iter().enumerate() {
        let target = (i + 1) as i64;
        if current < target {
            apply_one(conn, sql, target)?;
        }
    }
    Ok(())
}

/// Apply a single migration atomically: DDL + user_version bump in one txn.
fn apply_one(conn: &Connection, sql: &str, target: i64) -> Result<()> {
    conn.execute_batch("BEGIN IMMEDIATE")?;
    let step = (|| -> Result<()> {
        conn.execute_batch(sql)?;
        conn.pragma_update(None, "user_version", target)?;
        Ok(())
    })();
    match step {
        Ok(()) => conn.execute_batch("COMMIT"),
        Err(e) => {
            let _ = conn.execute_batch("ROLLBACK");
            Err(e)
        }
    }
}

/// Six required artefacts per agent (RULE 0.12 §completion bundle).
pub const REQUIRED_ARTEFACTS: &[&str] = &[
    "spec.md",
    "plan.md",
    "progress.json",
    "chatlog.md",
    "handoffs.md",
    "review.md",
];
