-- VEP P1 schema: multi-tenant, append-only event_log, provenance, dead-letter,
-- actor model. Row-level security enforced from the first migration (JL-003 / #4).
-- Run idempotently by app startup against the postgres container.

-- Extensions
CREATE EXTENSION IF NOT EXISTS vector;

-- Tenant registry (commitment #4: tenancy at schema level day 1)
CREATE TABLE IF NOT EXISTS tenant (
    id              TEXT PRIMARY KEY,
    name            TEXT NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Actor registry (agency first-class, JL-002): humans + digital agents, tier + audit
CREATE TABLE IF NOT EXISTS actor (
    id              TEXT PRIMARY KEY,
    tenant_id       TEXT NOT NULL REFERENCES tenant(id),
    kind            TEXT NOT NULL CHECK (kind IN ('human','digital')),
    authority_tier  INT  NOT NULL CHECK (authority_tier BETWEEN 0 AND 3), -- T0..T3
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Append-only event log (commitment #2). No UPDATE/DELETE granted to app role.
-- create_capture: 'create' | 'capture'  (new-market vs existing-demand probe)
-- confidence: 0..1 honest uncertainty
-- raw_ref: provenance pointer (source file / url / sheet cell / whatsapp msg id)
CREATE TABLE IF NOT EXISTS event_log (
    id              BIGSERIAL PRIMARY KEY,
    tenant_id       TEXT NOT NULL REFERENCES tenant(id),
    actor_id        TEXT NOT NULL,                       -- provenance (JL-002/#2)
    schema_ver      INT  NOT NULL DEFAULT 1,
    event_type      TEXT NOT NULL,
    payload         JSONB NOT NULL,
    create_capture  TEXT CHECK (create_capture IN ('create','capture','unknown')),
    confidence      DOUBLE PRECISION CHECK (confidence BETWEEN 0 AND 1),
    raw_ref         TEXT,                                -- provenance (JL-002/#2)
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_event_log_tenant ON event_log(tenant_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_event_log_type   ON event_log(tenant_id, event_type);

-- Dead-letter store for silent-loss prevention (JL-006/#2). Ingestion rejects land here.
CREATE TABLE IF NOT EXISTS dead_letter (
    id              BIGSERIAL PRIMARY KEY,
    tenant_id       TEXT,
    reason          TEXT NOT NULL,
    raw_payload     JSONB NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Row-level security: every table gated on tenant_id. App connects with a role that
-- can ONLY see its own tenant. DDL-level enforcement, not app-level.
ALTER TABLE event_log   ENABLE ROW LEVEL SECURITY;
ALTER TABLE actor       ENABLE ROW LEVEL SECURITY;
ALTER TABLE dead_letter ENABLE ROW LEVEL SECURITY;

-- The app connects as the role set below; we scope it per-tenant at connect time by
-- setting a session variable and matching it in the policy. (Simplified for P1 single
-- app connection; in P6 this moves to per-tenant JWT claims via Zitadel.)
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'vep_app') THEN
        CREATE ROLE vep_app LOGIN PASSWORD 'vep_app'; -- overridden by .env in runtime
    END IF;
END $$;
GRANT SELECT, INSERT ON ALL TABLES IN SCHEMA public TO vep_app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO vep_app;
-- Explicitly DENY mutations on the log (append-only at the privilege layer)
REVOKE UPDATE, DELETE ON event_log FROM vep_app;
REVOKE UPDATE, DELETE ON dead_letter FROM vep_app;

-- RLS policy: app role only sees rows matching its session tenant.
DROP POLICY IF EXISTS tenant_isolation_event_log ON event_log;
CREATE POLICY tenant_isolation_event_log ON event_log
    FOR ALL TO vep_app
    USING (tenant_id = current_setting('app.current_tenant', true));
DROP POLICY IF EXISTS tenant_isolation_actor ON actor;
CREATE POLICY tenant_isolation_actor ON actor
    FOR ALL TO vep_app
    USING (tenant_id = current_setting('app.current_tenant', true));
DROP POLICY IF EXISTS tenant_isolation_dead_letter ON dead_letter;
CREATE POLICY tenant_isolation_dead_letter ON dead_letter
    FOR ALL TO vep_app
    USING (tenant_id IS NULL OR tenant_id = current_setting('app.current_tenant', true));

-- Seed a default tenant + a human T0 owner actor so the spine has a first row.
INSERT INTO tenant (id, name) VALUES ('t_default','Default Tenant')
    ON CONFLICT (id) DO NOTHING;
INSERT INTO actor (id, tenant_id, kind, authority_tier)
    VALUES ('a_owner','t_default','human',0)
    ON CONFLICT (id) DO NOTHING;
