-- Research Capacitor — Supabase Schema
-- Run this in the Supabase SQL Editor to bootstrap the base database objects.
-- Apply order:
--   1. supabase/schema.sql
--   2. supabase/hardening.sql
--   3. supabase/reservation_runtime.sql
--
-- Responsibility:
--   - base enum, table, index, and trigger definitions only
--   - safe to rerun
--   - does not own the secure runtime RPCs

SET search_path TO public;

-- ============================================================
-- ENUMS
-- ============================================================

DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'node_role') THEN
        CREATE TYPE node_role AS ENUM ('provider', 'requester', 'both');
    END IF;
END;
$$;

DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'node_status') THEN
        CREATE TYPE node_status AS ENUM ('online', 'offline');
    END IF;
END;
$$;

DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'task_status') THEN
        CREATE TYPE task_status AS ENUM ('pending', 'assigned', 'running', 'completed', 'failed');
    END IF;
END;
$$;

DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'txn_type') THEN
        CREATE TYPE txn_type AS ENUM ('mint', 'burn', 'transfer', 'refund', 'reserve', 'release');
    END IF;
END;
$$;

-- ============================================================
-- TABLES
-- ============================================================

CREATE TABLE IF NOT EXISTS nodes (
    id          uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    name        text NOT NULL DEFAULT 'unnamed-node',
    role        node_role NOT NULL DEFAULT 'both',
    public_key  text NOT NULL DEFAULT '',
    gpu_info    jsonb NOT NULL DEFAULT '{}',
    unlimited_budget boolean NOT NULL DEFAULT false,
    status      node_status NOT NULL DEFAULT 'offline',
    last_heartbeat timestamptz,
    created_at  timestamptz NOT NULL DEFAULT now()
);

ALTER TABLE nodes
    ADD COLUMN IF NOT EXISTS unlimited_budget boolean NOT NULL DEFAULT false;

CREATE TABLE IF NOT EXISTS credit_balances (
    node_id     uuid PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
    balance     bigint NOT NULL DEFAULT 0 CHECK (balance >= 0),
    reserved_balance bigint NOT NULL DEFAULT 0 CHECK (reserved_balance >= 0),
    updated_at  timestamptz NOT NULL DEFAULT now()
);

CREATE TABLE IF NOT EXISTS research_jobs (
    id                      uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    requester_id            uuid NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
    status                  text NOT NULL DEFAULT 'active',
    objective               text NOT NULL DEFAULT 'autonomous_research',
    domain                  text NOT NULL DEFAULT '',
    model                   text NOT NULL DEFAULT '',
    seed_conjecture         text NOT NULL DEFAULT '',
    total_budget            bigint NOT NULL DEFAULT 0 CHECK (total_budget >= 0),
    budget_per_attempt      bigint NOT NULL DEFAULT 0 CHECK (budget_per_attempt >= 0),
    max_concurrent_episodes integer NOT NULL DEFAULT 1 CHECK (max_concurrent_episodes > 0),
    max_attempts_per_episode integer NOT NULL DEFAULT 4 CHECK (max_attempts_per_episode > 0),
    credits_committed       bigint NOT NULL DEFAULT 0 CHECK (credits_committed >= 0),
    latest_error            text NOT NULL DEFAULT '',
    metadata                jsonb NOT NULL DEFAULT '{}'::jsonb,
    created_at              timestamptz NOT NULL DEFAULT now(),
    updated_at              timestamptz NOT NULL DEFAULT now()
);

CREATE TABLE IF NOT EXISTS research_episodes (
    id                  uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    job_id               uuid REFERENCES research_jobs(id) ON DELETE CASCADE,
    requester_id        uuid NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
    status              text NOT NULL DEFAULT 'active',
    domain              text NOT NULL DEFAULT '',
    conjecture_text     text NOT NULL,
    formal_statement    text NOT NULL DEFAULT '',
    strategy_tag        text NOT NULL DEFAULT '',
    interestingness_score double precision NOT NULL DEFAULT 0,
    novelty_score       double precision NOT NULL DEFAULT 0,
    max_attempts        integer NOT NULL DEFAULT 8 CHECK (max_attempts > 0),
    budget_per_attempt  bigint NOT NULL DEFAULT 0 CHECK (budget_per_attempt >= 0),
    attempt_count       integer NOT NULL DEFAULT 0,
    verified_attempt_id uuid,
    metadata            jsonb NOT NULL DEFAULT '{}'::jsonb,
    created_at          timestamptz NOT NULL DEFAULT now(),
    updated_at          timestamptz NOT NULL DEFAULT now()
);

CREATE TABLE IF NOT EXISTS tasks (
    id              uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    requester_id    uuid NOT NULL REFERENCES nodes(id),
    provider_id     uuid REFERENCES nodes(id),
    research_episode_id uuid REFERENCES research_episodes(id) ON DELETE SET NULL,
    type            text NOT NULL DEFAULT 'lean_proof_gen',
    prompt          text NOT NULL,
    model           text NOT NULL DEFAULT '',
    status          task_status NOT NULL DEFAULT 'pending',
    credit_budget   bigint NOT NULL DEFAULT 0 CHECK (credit_budget >= 0),
    attempt_index   integer NOT NULL DEFAULT 0,
    parent_attempt_id uuid,
    task_metadata   jsonb NOT NULL DEFAULT '{}'::jsonb,
    attempt_count   integer NOT NULL DEFAULT 0,
    max_attempts    integer NOT NULL DEFAULT 3 CHECK (max_attempts > 0),
    lease_expires_at timestamptz,
    claimed_at      timestamptz,
    started_at      timestamptz,
    completed_at    timestamptz,
    failed_at       timestamptz,
    last_error      text NOT NULL DEFAULT '',
    created_at      timestamptz NOT NULL DEFAULT now(),
    updated_at      timestamptz NOT NULL DEFAULT now()
);

CREATE TABLE IF NOT EXISTS task_results (
    id              uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    task_id         uuid NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
    provider_id     uuid NOT NULL REFERENCES nodes(id),
    lean_code       text NOT NULL DEFAULT '',
    raw_output      text NOT NULL DEFAULT '',
    flops_used      bigint NOT NULL DEFAULT 0,
    mflops_used     bigint NOT NULL DEFAULT 0,
    billed_mflops   bigint NOT NULL DEFAULT 0,
    prompt_tokens   integer NOT NULL DEFAULT 0,
    generated_tokens integer NOT NULL DEFAULT 0,
    model_params    bigint NOT NULL DEFAULT 0,
    prefill_eval_ms double precision NOT NULL DEFAULT 0,
    decode_eval_ms  double precision NOT NULL DEFAULT 0,
    total_eval_ms   double precision NOT NULL DEFAULT 0,
    created_at      timestamptz NOT NULL DEFAULT now()
);

COMMENT ON COLUMN tasks.credit_budget IS
    'Exact credit subunits reserved for the task. One whole credit equals one TFLOP, stored as 1,000,000 integer subunits.';

COMMENT ON COLUMN research_jobs.total_budget IS
    'Exact credit subunits available to the job. One whole credit equals one TFLOP, stored as 1,000,000 integer subunits.';

COMMENT ON COLUMN research_jobs.budget_per_attempt IS
    'Exact credit subunits reserved per attempt. One whole credit equals one TFLOP, stored as 1,000,000 integer subunits.';

COMMENT ON COLUMN research_episodes.budget_per_attempt IS
    'Exact credit subunits reserved per attempt. One whole credit equals one TFLOP, stored as 1,000,000 integer subunits.';

COMMENT ON COLUMN task_results.flops_used IS
    'Exact raw model-work telemetry in FLOPs computed from model parameters and processed tokens.';

COMMENT ON COLUMN task_results.mflops_used IS
    'Legacy floored MFLOP view derived from flops_used for backward compatibility.';

COMMENT ON COLUMN task_results.billed_mflops IS
    'Legacy column name for exact settled credit subunits. One billed_mflops unit equals 0.000001 TFLOP credit.';

CREATE TABLE IF NOT EXISTS proof_attempts (
    id                  uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    episode_id          uuid NOT NULL REFERENCES research_episodes(id) ON DELETE CASCADE,
    task_id             uuid UNIQUE REFERENCES tasks(id) ON DELETE SET NULL,
    task_result_id      uuid UNIQUE REFERENCES task_results(id) ON DELETE SET NULL,
    requester_id        uuid NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
    provider_id         uuid REFERENCES nodes(id) ON DELETE SET NULL,
    parent_attempt_id   uuid REFERENCES proof_attempts(id) ON DELETE SET NULL,
    attempt_index       integer NOT NULL DEFAULT 1,
    task_type           text NOT NULL DEFAULT 'proof_attempt',
    strategy_tag        text NOT NULL DEFAULT '',
    conjecture_text     text NOT NULL DEFAULT '',
    formal_statement    text NOT NULL DEFAULT '',
    prompt              text NOT NULL DEFAULT '',
    generated_lean_code text NOT NULL DEFAULT '',
    raw_output          text NOT NULL DEFAULT '',
    verification_status text NOT NULL DEFAULT 'pending',
    verification_summary text NOT NULL DEFAULT '',
    normalized_code     text NOT NULL DEFAULT '',
    lean_stdout         text NOT NULL DEFAULT '',
    lean_stderr         text NOT NULL DEFAULT '',
    verifier_metadata   jsonb NOT NULL DEFAULT '{}'::jsonb,
    novelty_score       double precision NOT NULL DEFAULT 0,
    interestingness_score double precision NOT NULL DEFAULT 0,
    created_at          timestamptz NOT NULL DEFAULT now(),
    updated_at          timestamptz NOT NULL DEFAULT now(),
    verified_at         timestamptz
);

CREATE TABLE IF NOT EXISTS verified_lemmas (
    id                   uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    source_task_id       text NOT NULL DEFAULT '',
    source_prompt        text NOT NULL DEFAULT '',
    source_job_id        text NOT NULL DEFAULT '',
    source_episode_id    text NOT NULL DEFAULT '',
    source_attempt_id    text NOT NULL DEFAULT '',
    metadata             jsonb NOT NULL DEFAULT '{}'::jsonb,
    conjecture           text NOT NULL DEFAULT '',
    normalized_code      text NOT NULL DEFAULT '',
    verification_status  text NOT NULL DEFAULT '',
    verification_summary text NOT NULL DEFAULT '',
    compiler             text NOT NULL DEFAULT '',
    stdout               text NOT NULL DEFAULT '',
    stderr               text NOT NULL DEFAULT '',
    is_true              boolean NOT NULL DEFAULT true,
    stored_at            timestamptz NOT NULL DEFAULT now()
);

ALTER TABLE tasks
    ADD COLUMN IF NOT EXISTS research_episode_id uuid REFERENCES research_episodes(id) ON DELETE SET NULL,
    ADD COLUMN IF NOT EXISTS attempt_index integer NOT NULL DEFAULT 0,
    ADD COLUMN IF NOT EXISTS parent_attempt_id uuid,
    ADD COLUMN IF NOT EXISTS task_metadata jsonb NOT NULL DEFAULT '{}'::jsonb;

ALTER TABLE research_episodes
    ADD COLUMN IF NOT EXISTS job_id uuid REFERENCES research_jobs(id) ON DELETE CASCADE;

CREATE TABLE IF NOT EXISTS transactions (
    id              uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    from_node_id    uuid REFERENCES nodes(id),
    to_node_id      uuid REFERENCES nodes(id),
    amount          bigint NOT NULL CHECK (amount > 0),
    type            txn_type NOT NULL,
    task_id         uuid REFERENCES tasks(id),
    memo            text DEFAULT '',
    created_at      timestamptz NOT NULL DEFAULT now()
);

-- ============================================================
-- INDEXES
-- ============================================================

CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
CREATE INDEX IF NOT EXISTS idx_tasks_requester ON tasks(requester_id);
CREATE INDEX IF NOT EXISTS idx_tasks_provider ON tasks(provider_id);
CREATE INDEX IF NOT EXISTS idx_tasks_lease ON tasks(lease_expires_at);
CREATE INDEX IF NOT EXISTS idx_tasks_episode ON tasks(research_episode_id);
CREATE INDEX IF NOT EXISTS idx_tasks_status_created_at ON tasks(status, created_at);
CREATE INDEX IF NOT EXISTS idx_tasks_status_lease ON tasks(status, lease_expires_at);
CREATE INDEX IF NOT EXISTS idx_research_jobs_requester ON research_jobs(requester_id);
CREATE INDEX IF NOT EXISTS idx_research_jobs_status ON research_jobs(status);
CREATE INDEX IF NOT EXISTS idx_transactions_from ON transactions(from_node_id);
CREATE INDEX IF NOT EXISTS idx_transactions_to ON transactions(to_node_id);
CREATE INDEX IF NOT EXISTS idx_transactions_task ON transactions(task_id);
CREATE INDEX IF NOT EXISTS idx_transactions_created_at ON transactions(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_task_results_task ON task_results(task_id);
CREATE INDEX IF NOT EXISTS idx_research_episodes_requester ON research_episodes(requester_id);
CREATE INDEX IF NOT EXISTS idx_research_episodes_status ON research_episodes(status);
CREATE INDEX IF NOT EXISTS idx_research_episodes_job ON research_episodes(job_id);
CREATE INDEX IF NOT EXISTS idx_proof_attempts_episode ON proof_attempts(episode_id);
CREATE INDEX IF NOT EXISTS idx_proof_attempts_episode_attempt ON proof_attempts(episode_id, attempt_index);
CREATE INDEX IF NOT EXISTS idx_proof_attempts_task ON proof_attempts(task_id);
CREATE INDEX IF NOT EXISTS idx_proof_attempts_status ON proof_attempts(verification_status);
CREATE INDEX IF NOT EXISTS idx_verified_lemmas_stored_at ON verified_lemmas(stored_at DESC);
CREATE INDEX IF NOT EXISTS idx_verified_lemmas_source_attempt ON verified_lemmas(source_attempt_id);
CREATE INDEX IF NOT EXISTS idx_nodes_status ON nodes(status);

-- ============================================================
-- TRIGGER: auto-update updated_at on tasks
-- ============================================================

CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = now();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS tasks_updated_at ON tasks;

CREATE TRIGGER tasks_updated_at
    BEFORE UPDATE ON tasks
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

DROP TRIGGER IF EXISTS research_jobs_updated_at ON research_jobs;

CREATE TRIGGER research_jobs_updated_at
    BEFORE UPDATE ON research_jobs
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

DROP TRIGGER IF EXISTS research_episodes_updated_at ON research_episodes;

CREATE TRIGGER research_episodes_updated_at
    BEFORE UPDATE ON research_episodes
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

DROP TRIGGER IF EXISTS proof_attempts_updated_at ON proof_attempts;

CREATE TRIGGER proof_attempts_updated_at
    BEFORE UPDATE ON proof_attempts
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

-- ============================================================
-- RUNTIME RPCS
-- Secure runtime RPCs are defined in supabase/hardening.sql.
-- Intentionally not recreated here so rerunning schema.sql does not
-- overwrite newer hardened functions.
-- ============================================================
