"""
cortex/storage/migrations/v3_initial.py — Baseline Cortex v3 schema.

This migration represents the initial database schema.  It is idempotent
(all DDL uses IF NOT EXISTS) so it is safe to apply against an already-
created database.

New databases: this is the first migration applied (version 0 → 3).
Existing databases: this migration is skipped because schema_version
is already '3' (or higher).
"""

VERSION = 3
DESCRIPTION = "Cortex v3 initial schema: entities, episodes, claims, brain graph, embeddings, FTS"

# ---------------------------------------------------------------------------
# SQL_UP — extracted verbatim from cortex/storage/migrations/v3.sql
# FTS5 virtual tables are left as plain CREATE VIRTUAL TABLE statements;
# the runner splits on ";" which handles them correctly.
# ---------------------------------------------------------------------------

SQL_UP = """
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;

CREATE TABLE IF NOT EXISTS entity (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    owner_id        TEXT NOT NULL DEFAULT 'default',
    canonical_name  TEXT NOT NULL,
    entity_type     TEXT NOT NULL DEFAULT 'unknown',
    description     TEXT,
    metadata_json   TEXT,
    first_seen      TEXT NOT NULL DEFAULT (datetime('now')),
    last_seen       TEXT NOT NULL DEFAULT (datetime('now')),
    mention_count   INTEGER DEFAULT 1,
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at      TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX IF NOT EXISTS idx_entity_owner ON entity(owner_id);
CREATE INDEX IF NOT EXISTS idx_entity_type ON entity(owner_id, entity_type);
CREATE INDEX IF NOT EXISTS idx_entity_name ON entity(canonical_name);

CREATE TABLE IF NOT EXISTS entity_alias (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    entity_id       TEXT NOT NULL,
    alias           TEXT NOT NULL,
    alias_type      TEXT DEFAULT 'name',
    source          TEXT DEFAULT 'extracted',
    confidence      REAL DEFAULT 1.0,
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE
);

CREATE INDEX IF NOT EXISTS idx_alias_entity ON entity_alias(entity_id);
CREATE INDEX IF NOT EXISTS idx_alias_text ON entity_alias(alias COLLATE NOCASE);

CREATE TABLE IF NOT EXISTS cornerstone_memory (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    owner_id        TEXT NOT NULL DEFAULT 'default',
    label           TEXT NOT NULL,
    content         TEXT NOT NULL,
    golden_copy     TEXT NOT NULL,
    golden_hash     TEXT NOT NULL,
    current_hash    TEXT NOT NULL,
    drift_score     REAL DEFAULT 0.0,
    last_drift_check TEXT,
    token_count     INTEGER NOT NULL,
    injection_order INTEGER DEFAULT 0,
    sealed_at       TEXT NOT NULL DEFAULT (datetime('now')),
    sealed_by       TEXT NOT NULL DEFAULT 'operator',
    revision        INTEGER DEFAULT 1,
    revision_history TEXT DEFAULT '[]',
    status          TEXT DEFAULT 'active',
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at      TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX IF NOT EXISTS idx_cs_owner ON cornerstone_memory(owner_id, status, injection_order);

CREATE TABLE IF NOT EXISTS session_record (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    owner_id        TEXT NOT NULL DEFAULT 'default',
    session_id      TEXT NOT NULL,
    started_at      TEXT NOT NULL,
    ended_at        TEXT,
    status          TEXT DEFAULT 'awake',
    duration_seconds INTEGER,
    fatigue_score   REAL DEFAULT 0.0,
    compaction_count INTEGER DEFAULT 0,
    turn_count      INTEGER DEFAULT 0,
    summary         TEXT,
    sleep_report    TEXT,
    dream_report    TEXT,
    memories_created  INTEGER DEFAULT 0,
    memories_promoted INTEGER DEFAULT 0,
    memories_pruned   INTEGER DEFAULT 0,
    created_at      TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_session_sid ON session_record(owner_id, session_id);
CREATE INDEX IF NOT EXISTS idx_session_status ON session_record(owner_id, status);
CREATE INDEX IF NOT EXISTS idx_session_time ON session_record(started_at DESC);

CREATE TABLE IF NOT EXISTS episode_event (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    owner_id        TEXT NOT NULL DEFAULT 'default',
    session_id      TEXT,
    actor_id        TEXT NOT NULL,
    ts              TEXT NOT NULL,
    kind            TEXT NOT NULL,
    raw_text        TEXT,
    tool_trace_ref  TEXT,
    source_uri      TEXT,
    metadata_json   TEXT,
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    FOREIGN KEY (session_id) REFERENCES session_record(id)
);

CREATE INDEX IF NOT EXISTS idx_episode_owner_ts ON episode_event(owner_id, ts);
CREATE INDEX IF NOT EXISTS idx_episode_session ON episode_event(session_id, ts);
CREATE INDEX IF NOT EXISTS idx_episode_actor ON episode_event(owner_id, actor_id);

CREATE TABLE IF NOT EXISTS semantic_claim (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    owner_id        TEXT NOT NULL DEFAULT 'default',
    memory_type     TEXT NOT NULL DEFAULT 'session',
    subject         TEXT NOT NULL,
    subject_entity_id TEXT,
    predicate       TEXT NOT NULL,
    object_value    TEXT NOT NULL,
    object_entity_id TEXT,
    datatype        TEXT DEFAULT 'text',
    confidence      REAL DEFAULT 0.5,
    sensitivity     TEXT DEFAULT 'normal',
    scope           TEXT DEFAULT 'user',
    valid_from      TEXT,
    valid_to        TEXT,
    status          TEXT DEFAULT 'active',
    superseded_by   TEXT,
    last_accessed   TEXT,
    access_count    INTEGER DEFAULT 0,
    source_session  TEXT,
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at      TEXT NOT NULL DEFAULT (datetime('now')),
    FOREIGN KEY (superseded_by) REFERENCES semantic_claim(id),
    FOREIGN KEY (source_session) REFERENCES session_record(id),
    FOREIGN KEY (subject_entity_id) REFERENCES entity(id),
    FOREIGN KEY (object_entity_id) REFERENCES entity(id)
);

CREATE INDEX IF NOT EXISTS idx_claim_owner_type ON semantic_claim(owner_id, memory_type, status);
CREATE INDEX IF NOT EXISTS idx_claim_subject ON semantic_claim(owner_id, subject, status);
CREATE INDEX IF NOT EXISTS idx_claim_entity ON semantic_claim(subject_entity_id);
CREATE INDEX IF NOT EXISTS idx_claim_status ON semantic_claim(owner_id, status);
CREATE INDEX IF NOT EXISTS idx_claim_cooling ON semantic_claim(owner_id, status, last_accessed)
    WHERE status IN ('active', 'cooling');
CREATE INDEX IF NOT EXISTS idx_claim_sensitivity ON semantic_claim(owner_id, sensitivity)
    WHERE sensitivity != 'normal';

CREATE TABLE IF NOT EXISTS claim_provenance (
    id                  TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    claim_id            TEXT NOT NULL,
    episode_event_id    TEXT NOT NULL,
    span_start          INTEGER,
    span_end            INTEGER,
    extraction_method   TEXT,
    extractor_model_id  TEXT,
    extraction_prompt_ver TEXT,
    created_at          TEXT NOT NULL DEFAULT (datetime('now')),
    FOREIGN KEY (claim_id) REFERENCES semantic_claim(id) ON DELETE CASCADE,
    FOREIGN KEY (episode_event_id) REFERENCES episode_event(id)
);

CREATE INDEX IF NOT EXISTS idx_prov_claim ON claim_provenance(claim_id);
CREATE INDEX IF NOT EXISTS idx_prov_episode ON claim_provenance(episode_event_id);

CREATE TABLE IF NOT EXISTS procedure_memory (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    owner_id        TEXT NOT NULL DEFAULT 'default',
    name            TEXT NOT NULL,
    kind            TEXT NOT NULL,
    trigger_desc    TEXT,
    content_text    TEXT,
    content_json    TEXT,
    success_count   INTEGER DEFAULT 0,
    failure_count   INTEGER DEFAULT 0,
    last_used_at    TEXT,
    source_type     TEXT,
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at      TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX IF NOT EXISTS idx_proc_owner ON procedure_memory(owner_id);

CREATE TABLE IF NOT EXISTS brain_graph_index (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    owner_id        TEXT NOT NULL DEFAULT 'default',
    name            TEXT NOT NULL,
    source_type     TEXT NOT NULL,
    source_uri      TEXT,
    source_hash     TEXT,
    embedding_model_id TEXT,
    builder_prompt_ver TEXT,
    supersedes_id   TEXT,
    node_count      INTEGER DEFAULT 0,
    edge_count      INTEGER DEFAULT 0,
    last_indexed    TEXT,
    index_config    TEXT,
    status          TEXT DEFAULT 'building',
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    FOREIGN KEY (supersedes_id) REFERENCES brain_graph_index(id)
);

CREATE INDEX IF NOT EXISTS idx_bgi_owner ON brain_graph_index(owner_id, status);

CREATE TABLE IF NOT EXISTS brain_graph_node (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    graph_id        TEXT NOT NULL,
    node_type       TEXT NOT NULL,
    label           TEXT NOT NULL,
    content         TEXT,
    compact_repr    TEXT,
    depth           INTEGER DEFAULT 0,
    parent_id       TEXT,
    metadata_json   TEXT,
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    FOREIGN KEY (graph_id) REFERENCES brain_graph_index(id) ON DELETE CASCADE,
    FOREIGN KEY (parent_id) REFERENCES brain_graph_node(id)
);

CREATE INDEX IF NOT EXISTS idx_bgn_graph ON brain_graph_node(graph_id);
CREATE INDEX IF NOT EXISTS idx_bgn_type ON brain_graph_node(graph_id, node_type);
CREATE INDEX IF NOT EXISTS idx_bgn_parent ON brain_graph_node(parent_id);

CREATE TABLE IF NOT EXISTS brain_graph_edge (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    graph_id        TEXT NOT NULL,
    source_id       TEXT NOT NULL,
    target_id       TEXT NOT NULL,
    edge_type       TEXT NOT NULL,
    weight          REAL DEFAULT 1.0,
    metadata_json   TEXT,
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    FOREIGN KEY (graph_id) REFERENCES brain_graph_index(id) ON DELETE CASCADE,
    FOREIGN KEY (source_id) REFERENCES brain_graph_node(id) ON DELETE CASCADE,
    FOREIGN KEY (target_id) REFERENCES brain_graph_node(id) ON DELETE CASCADE
);

CREATE INDEX IF NOT EXISTS idx_bge_graph ON brain_graph_edge(graph_id);
CREATE INDEX IF NOT EXISTS idx_bge_source ON brain_graph_edge(source_id);
CREATE INDEX IF NOT EXISTS idx_bge_target ON brain_graph_edge(target_id);

CREATE TABLE IF NOT EXISTS memory_edge (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    owner_id        TEXT NOT NULL DEFAULT 'default',
    source_type     TEXT NOT NULL,
    source_id       TEXT NOT NULL,
    target_type     TEXT NOT NULL,
    target_id       TEXT NOT NULL,
    edge_type       TEXT NOT NULL,
    confidence      REAL DEFAULT 1.0,
    metadata_json   TEXT,
    created_at      TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX IF NOT EXISTS idx_medge_owner ON memory_edge(owner_id);
CREATE INDEX IF NOT EXISTS idx_medge_source ON memory_edge(source_type, source_id);
CREATE INDEX IF NOT EXISTS idx_medge_target ON memory_edge(target_type, target_id);
CREATE INDEX IF NOT EXISTS idx_medge_type ON memory_edge(owner_id, edge_type);

CREATE TABLE IF NOT EXISTS embedding_index (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    item_type       TEXT NOT NULL,
    item_id         TEXT NOT NULL,
    embedding       BLOB,
    model_id        TEXT NOT NULL,
    created_at      TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX IF NOT EXISTS idx_emb_item ON embedding_index(item_type, item_id);
CREATE INDEX IF NOT EXISTS idx_emb_model ON embedding_index(model_id);

CREATE VIRTUAL TABLE IF NOT EXISTS fts_claims USING fts5(
    claim_id UNINDEXED,
    content,
    tokenize='porter unicode61'
);

CREATE VIRTUAL TABLE IF NOT EXISTS fts_episodes USING fts5(
    event_id UNINDEXED,
    content,
    tokenize='porter unicode61'
);

CREATE TABLE IF NOT EXISTS retrieval_log (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    owner_id        TEXT NOT NULL DEFAULT 'default',
    session_id      TEXT,
    query_text      TEXT NOT NULL,
    mode            TEXT NOT NULL,
    token_budget    INTEGER,
    tokens_used     INTEGER,
    candidates_json TEXT,
    rerank_model    TEXT,
    selected_json   TEXT,
    final_context_hash TEXT,
    latency_ms      INTEGER,
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    FOREIGN KEY (session_id) REFERENCES session_record(id)
);

CREATE INDEX IF NOT EXISTS idx_rlog_owner ON retrieval_log(owner_id, created_at DESC);

CREATE TABLE IF NOT EXISTS drift_log (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    cornerstone_id  TEXT NOT NULL,
    drift_score     REAL NOT NULL,
    semantic_sim    REAL,
    length_ratio    REAL,
    phrase_preservation REAL,
    action_taken    TEXT NOT NULL,
    current_text    TEXT,
    golden_text     TEXT,
    checked_at      TEXT NOT NULL,
    FOREIGN KEY (cornerstone_id) REFERENCES cornerstone_memory(id)
);

CREATE INDEX IF NOT EXISTS idx_drift_cs ON drift_log(cornerstone_id, checked_at DESC);

CREATE TABLE IF NOT EXISTS deprecation_log (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    claim_id        TEXT NOT NULL,
    session_id      TEXT,
    action          TEXT NOT NULL,
    reason          TEXT,
    classifier      TEXT,
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    FOREIGN KEY (claim_id) REFERENCES semantic_claim(id)
);

CREATE INDEX IF NOT EXISTS idx_depr_session ON deprecation_log(session_id);

CREATE TABLE IF NOT EXISTS memory_feedback (
    id              TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    owner_id        TEXT NOT NULL DEFAULT 'default',
    target_type     TEXT NOT NULL,
    target_id       TEXT NOT NULL,
    feedback_type   TEXT NOT NULL,
    feedback_value  TEXT,
    source          TEXT DEFAULT 'operator',
    status          TEXT DEFAULT 'pending',
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    applied_at      TEXT
);

CREATE INDEX IF NOT EXISTS idx_fb_owner ON memory_feedback(owner_id, status);
CREATE INDEX IF NOT EXISTS idx_fb_target ON memory_feedback(target_type, target_id);

CREATE TABLE IF NOT EXISTS cortex_config (
    key             TEXT PRIMARY KEY,
    value           TEXT NOT NULL,
    updated_at      TEXT NOT NULL DEFAULT (datetime('now'))
);

INSERT OR IGNORE INTO cortex_config (key, value) VALUES
    ('schema_version', '3'),
    ('embedding_model', 'text-embedding-3-small'),
    ('embedding_dim', '1536'),
    ('extraction_model', 'gpt-4.1-nano-2025-04-14'),
    ('reconciliation_model', 'gpt-4.1-nano-2025-04-14'),
    ('cornerstone_token_budget', '800'),
    ('retrieval_token_budget', '3000'),
    ('total_memory_token_budget', '4000'),
    ('drift_check_threshold', '0.3'),
    ('drift_restore_threshold', '0.5'),
    ('anti_compression_confidence', '0.9'),
    ('deprecation_cooling_days', '14'),
    ('deprecation_archive_days', '90'),
    ('session_confidence_default', '0.5'),
    ('promoted_confidence_default', '0.8'),
    ('session_fatigue_nap_threshold', '0.7'),
    ('session_fatigue_sleep_threshold', '0.9'),
    ('dream_cycle_hour_utc', '8'),
    ('dream_cycle_idle_timeout_sec', '3600'),
    ('cornerstone_auto_apply', 'false'),
    ('owner_id', 'default'),
    ('last_consolidation', ''),
    ('last_dream_cycle', '')
"""

SQL_DOWN = """
-- v3 is the baseline schema. Downgrading would destroy all data.
-- To rollback: restore from backup.
-- DROP TABLE IF EXISTS cortex_config;
-- DROP TABLE IF EXISTS memory_feedback;
-- ... (full teardown omitted intentionally)
"""
