-- AwoooP Phase 1 Batch 1: 現有四表加 project_id + RLS
-- 2026-05-04 ogt + Claude Sonnet 4.6（ADR-118 Batch 1，C-3/C-4 db-expert 修正版）
-- 2026-05-04 critic 修正版：ADD CONSTRAINT IF NOT EXISTS 不存在於 PG → 改用 DO 塊檢查 pg_constraint
--
-- 對象：incidents / knowledge_entries / playbooks / audit_logs
-- 這四張表是高頻寫入表，採「三步式 migration」避免長時間鎖表：
--
--   Step A: ADD COLUMN nullable（metadata-only，瞬間）
--   Step B: 分批回填（每批 5000 筆，外部腳本呼叫）
--   Step C: NOT VALID CHECK → VALIDATE（SHARE UPDATE EXCLUSIVE，不擋讀寫）
--            → SET NOT NULL（PG 12+ 利用已驗證 check，不掃表）
--            → SET DEFAULT 'awoooi'
--
-- ⚠️  執行前必確認：
--     1. awooop_phase1_control_plane_2026-05-04.sql 已執行（awooop_projects 表存在）
--     2. apps/api 已 deploy 「SET LOCAL app.project_id」版本，rollout 100%
--     3. 31 個 background loop 改用 awooop_platform_admin role（PR-10）
--     4. 量測各表體量（見下方 pre-migration check query）
--
-- Pre-migration check：
--   SELECT relname, n_live_tup, pg_size_pretty(pg_total_relation_size(oid))
--   FROM pg_class
--   WHERE relname IN ('incidents','knowledge_entries','playbooks','audit_logs');
--
-- 分批回填腳本：
--   apps/api/scripts/awooop_phase1_batch1_backfill.py（另行提供）
--
-- ⚠️  RLS 是 fail-closed：
--   SET LOCAL app.project_id 未設 → 讀不到任何資料（C-4 修正）
--   WITH CHECK 防止 INSERT 寫入錯誤 tenant
--
-- 回滾路徑：
--   ALTER TABLE incidents         DISABLE ROW LEVEL SECURITY;
--   DROP POLICY IF EXISTS incidents_tenant_isolation         ON incidents;
--   DROP POLICY IF EXISTS knowledge_entries_tenant_isolation ON knowledge_entries;
--   DROP POLICY IF EXISTS playbooks_tenant_isolation         ON playbooks;
--   DROP POLICY IF EXISTS audit_logs_tenant_isolation        ON audit_logs;
--   ALTER TABLE incidents         DISABLE ROW LEVEL SECURITY;
--   ALTER TABLE knowledge_entries DISABLE ROW LEVEL SECURITY;
--   ALTER TABLE playbooks         DISABLE ROW LEVEL SECURITY;
--   ALTER TABLE audit_logs        DISABLE ROW LEVEL SECURITY;
--   ALTER TABLE incidents         DROP COLUMN IF EXISTS project_id;
--   ALTER TABLE knowledge_entries DROP COLUMN IF EXISTS project_id;
--   ALTER TABLE playbooks         DROP COLUMN IF EXISTS project_id;
--   ALTER TABLE audit_logs        DROP COLUMN IF EXISTS project_id;
-- ---------------------------------------------------------------------------


-- ===========================
-- STEP A: ADD COLUMN（nullable，瞬間取鎖，不重寫表）
-- ===========================
-- 一次只做 ADD COLUMN，讓 AccessExclusiveLock 最短

DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM information_schema.columns
        WHERE table_name = 'incidents' AND column_name = 'project_id'
    ) THEN
        ALTER TABLE incidents ADD COLUMN project_id VARCHAR(64);
    END IF;
END $$;

DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM information_schema.columns
        WHERE table_name = 'knowledge_entries' AND column_name = 'project_id'
    ) THEN
        ALTER TABLE knowledge_entries ADD COLUMN project_id VARCHAR(64);
    END IF;
END $$;

DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM information_schema.columns
        WHERE table_name = 'playbooks' AND column_name = 'project_id'
    ) THEN
        ALTER TABLE playbooks ADD COLUMN project_id VARCHAR(64);
    END IF;
END $$;

DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM information_schema.columns
        WHERE table_name = 'audit_logs' AND column_name = 'project_id'
    ) THEN
        ALTER TABLE audit_logs ADD COLUMN project_id VARCHAR(64);
    END IF;
END $$;


-- ===========================
-- STEP B: 分批回填（外部腳本）
-- ===========================
-- 此步驟由 apps/api/scripts/awooop_phase1_batch1_backfill.py 執行
-- 每批 UPDATE ... WHERE project_id IS NULL LIMIT 5000
-- 完成條件：SELECT count(*) FROM incidents WHERE project_id IS NULL; → 0
--
-- 快速驗證（執行此 SQL 前必須確認回填完成）：
-- SELECT
--     'incidents' as tbl, count(*) as null_count FROM incidents WHERE project_id IS NULL
--   UNION ALL SELECT 'knowledge_entries', count(*) FROM knowledge_entries WHERE project_id IS NULL
--   UNION ALL SELECT 'playbooks', count(*) FROM playbooks WHERE project_id IS NULL
--   UNION ALL SELECT 'audit_logs', count(*) FROM audit_logs WHERE project_id IS NULL;
-- 所有 null_count 必須為 0，否則停止。
--
-- ⚠️  回填完成確認後才可繼續執行 Step C


-- ===========================
-- STEP C: NOT NULL 強制 + DEFAULT + Index + RLS
-- ===========================
-- PostgreSQL 12+：NOT VALID CHECK → VALIDATE → SET NOT NULL
-- VALIDATE 只取 SHARE UPDATE EXCLUSIVE，不擋讀寫
-- SET NOT NULL 在 VALIDATE 後不再掃表（利用 check constraint 証明）

-- --- incidents ---

-- PostgreSQL 無 ADD CONSTRAINT IF NOT EXISTS，改用 DO 塊檢查 pg_constraint
DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint
         WHERE conname = 'chk_incidents_project_id_not_null'
           AND conrelid = 'incidents'::regclass
    ) THEN
        ALTER TABLE incidents
            ADD CONSTRAINT chk_incidents_project_id_not_null
            CHECK (project_id IS NOT NULL) NOT VALID;
    END IF;
END $$;

ALTER TABLE incidents
    VALIDATE CONSTRAINT chk_incidents_project_id_not_null;

ALTER TABLE incidents ALTER COLUMN project_id SET NOT NULL;
ALTER TABLE incidents ALTER COLUMN project_id SET DEFAULT 'awoooi';
ALTER TABLE incidents DROP CONSTRAINT IF EXISTS chk_incidents_project_id_not_null;

CREATE INDEX IF NOT EXISTS idx_incidents_project_id ON incidents (project_id);

ALTER TABLE incidents ENABLE ROW LEVEL SECURITY;
ALTER TABLE incidents FORCE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS incidents_tenant_isolation ON incidents;
CREATE POLICY incidents_tenant_isolation ON incidents
    FOR ALL TO awooop_app
    USING (project_id = current_setting('app.project_id', TRUE))
    WITH CHECK (project_id = current_setting('app.project_id', TRUE));


-- --- knowledge_entries ---

DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint
         WHERE conname = 'chk_km_project_id_not_null'
           AND conrelid = 'knowledge_entries'::regclass
    ) THEN
        ALTER TABLE knowledge_entries
            ADD CONSTRAINT chk_km_project_id_not_null
            CHECK (project_id IS NOT NULL) NOT VALID;
    END IF;
END $$;

ALTER TABLE knowledge_entries
    VALIDATE CONSTRAINT chk_km_project_id_not_null;

ALTER TABLE knowledge_entries ALTER COLUMN project_id SET NOT NULL;
ALTER TABLE knowledge_entries ALTER COLUMN project_id SET DEFAULT 'awoooi';
ALTER TABLE knowledge_entries DROP CONSTRAINT IF EXISTS chk_km_project_id_not_null;

CREATE INDEX IF NOT EXISTS idx_knowledge_entries_project_id ON knowledge_entries (project_id);

ALTER TABLE knowledge_entries ENABLE ROW LEVEL SECURITY;
ALTER TABLE knowledge_entries FORCE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS knowledge_entries_tenant_isolation ON knowledge_entries;
CREATE POLICY knowledge_entries_tenant_isolation ON knowledge_entries
    FOR ALL TO awooop_app
    USING (project_id = current_setting('app.project_id', TRUE))
    WITH CHECK (project_id = current_setting('app.project_id', TRUE));


-- --- playbooks ---

DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint
         WHERE conname = 'chk_playbooks_project_id_not_null'
           AND conrelid = 'playbooks'::regclass
    ) THEN
        ALTER TABLE playbooks
            ADD CONSTRAINT chk_playbooks_project_id_not_null
            CHECK (project_id IS NOT NULL) NOT VALID;
    END IF;
END $$;

ALTER TABLE playbooks
    VALIDATE CONSTRAINT chk_playbooks_project_id_not_null;

ALTER TABLE playbooks ALTER COLUMN project_id SET NOT NULL;
ALTER TABLE playbooks ALTER COLUMN project_id SET DEFAULT 'awoooi';
ALTER TABLE playbooks DROP CONSTRAINT IF EXISTS chk_playbooks_project_id_not_null;

CREATE INDEX IF NOT EXISTS idx_playbooks_project_id ON playbooks (project_id);

ALTER TABLE playbooks ENABLE ROW LEVEL SECURITY;
ALTER TABLE playbooks FORCE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS playbooks_tenant_isolation ON playbooks;
CREATE POLICY playbooks_tenant_isolation ON playbooks
    FOR ALL TO awooop_app
    USING (project_id = current_setting('app.project_id', TRUE))
    WITH CHECK (project_id = current_setting('app.project_id', TRUE));


-- --- audit_logs ---

DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint
         WHERE conname = 'chk_audit_project_id_not_null'
           AND conrelid = 'audit_logs'::regclass
    ) THEN
        ALTER TABLE audit_logs
            ADD CONSTRAINT chk_audit_project_id_not_null
            CHECK (project_id IS NOT NULL) NOT VALID;
    END IF;
END $$;

ALTER TABLE audit_logs
    VALIDATE CONSTRAINT chk_audit_project_id_not_null;

ALTER TABLE audit_logs ALTER COLUMN project_id SET NOT NULL;
ALTER TABLE audit_logs ALTER COLUMN project_id SET DEFAULT 'awoooi';
ALTER TABLE audit_logs DROP CONSTRAINT IF EXISTS chk_audit_project_id_not_null;

CREATE INDEX IF NOT EXISTS idx_audit_logs_project_id ON audit_logs (project_id);

ALTER TABLE audit_logs ENABLE ROW LEVEL SECURITY;
ALTER TABLE audit_logs FORCE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS audit_logs_tenant_isolation ON audit_logs;
CREATE POLICY audit_logs_tenant_isolation ON audit_logs
    FOR ALL TO awooop_app
    USING (project_id = current_setting('app.project_id', TRUE))
    WITH CHECK (project_id = current_setting('app.project_id', TRUE));


-- ===========================
-- 驗收查詢
-- ===========================
-- SELECT tablename, rowsecurity, forcerowsecurity FROM pg_tables
--   WHERE tablename IN ('incidents','knowledge_entries','playbooks','audit_logs');
--
-- -- RLS fail-closed 測試（需 awooop_app role 執行）：
-- SET ROLE awooop_app;
-- SET LOCAL app.project_id = 'ewoooc';
-- SELECT count(*) FROM incidents;  -- 應 = 0（無 ewoooc 資料）
-- SET LOCAL app.project_id = 'awoooi';
-- SELECT count(*) FROM incidents;  -- 應 = 全部既有資料筆數
-- RESET ROLE;
--
-- -- 確認無 NULL project_id：
-- SELECT count(*) FROM incidents         WHERE project_id IS NULL;  -- = 0
-- SELECT count(*) FROM knowledge_entries WHERE project_id IS NULL;  -- = 0
-- SELECT count(*) FROM playbooks         WHERE project_id IS NULL;  -- = 0
-- SELECT count(*) FROM audit_logs        WHERE project_id IS NULL;  -- = 0
