-- 145_insight.sql
-- #948 Part A (ТЗ §7.13/§8.9): manual-entry "insight" — аналитик пишет свободную
-- заметку/intel про локацию или участок, с пометкой «непублично» (is_confidential).
--
-- Access control: backend rbac_guard (app/main.py) хард-блокирует ТОЛЬКО
-- /api/v1/admin/*. На /api/v1/insights pilot отсекается FRONTEND-овым RouteGuard
-- (allowed_paths из /me) + Caddy, НЕ бэкендом; до эндпоинта доходят analyst+admin.
-- is_confidential — хранимая маркировка (per-record backend-gate НЕТ; будущий gate
-- публичной выдачи), сейчас просто хранимый+фильтруемый флаг (политика #962: analyst
-- видит конфиденциальные данные). created_by фиксирует автора (из X-Authenticated-User).
--
-- Location ref: пока plain TEXT district + опциональный cad_num (ссылка на участок).
-- Part B (#948, §8.2) промоутит district в Location FK — НЕ в этом таске.
--
-- Audit (§19, интеграция с #962): WRITE-запросы (POST/PUT/DELETE) аудируются
-- middleware app/core/audit_middleware.py с action='insight_write'. Поэтому ниже
-- (блок 4) аддитивно расширяем CHECK audit_log.action, чтобы 'insight_write'
-- проходил констрейнт. Это idempotent ALTER (DROP IF EXISTS + ADD) — НЕ правит
-- 144_audit_log.sql (тот уже применён на проде/смёржен; правка применённой
-- миграции не переиграется через _schema_migrations).
--
-- Deploy: auto-applied deploy.yml через _schema_migrations (ровно один раз NN=145).
-- Idempotent: CREATE TABLE/INDEX IF NOT EXISTS + guarded CHECK.

BEGIN;

-- ── 1. Таблица insight ────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS insight (
    id              BIGSERIAL PRIMARY KEY,
    created_by      TEXT        NOT NULL,          -- автор, из X-Authenticated-User
    title           TEXT        NOT NULL,          -- заголовок заметки
    body            TEXT        NOT NULL,          -- тело заметки (free-form intel)
    category        TEXT,                          -- competition|permitting|demand|risk|other
    is_confidential BOOLEAN     NOT NULL DEFAULT FALSE,  -- «непублично» (§7.13)
    district        TEXT,                          -- location ref (Part B → Location FK)
    cad_num         TEXT,                          -- опциональная ссылка на участок
    lon             DOUBLE PRECISION CHECK (lon IS NULL OR lon BETWEEN -180 AND 180),
    lat             DOUBLE PRECISION CHECK (lat IS NULL OR lat BETWEEN -90 AND 90),
    -- Опциональный map-pin: GENERATED из lon/lat (как user_custom_pois.geom #254).
    -- NULL если координаты не заданы (обе должны быть заданы вместе).
    geom            GEOMETRY(POINT, 4326) GENERATED ALWAYS AS (
                        CASE
                            WHEN lon IS NOT NULL AND lat IS NOT NULL
                            THEN ST_SetSRID(ST_MakePoint(lon, lat), 4326)
                        END
                    ) STORED,
    source          TEXT,                          -- источник intel (опционально)
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

COMMENT ON TABLE insight IS
    'Manual-entry аналитические заметки/intel про локацию или участок (#948 Part A, '
    'ТЗ §7.13/§8.9). is_confidential = маркировка «непублично». created_by = автор '
    'из X-Authenticated-User. district — plain TEXT (Part B промоутит в Location FK).';
COMMENT ON COLUMN insight.is_confidential IS
    '«Непублично» (§7.13): маркировка чувствительной заметки. Сейчас хранимый+'
    'фильтруемый флаг; будущий gate публичной выдачи.';
COMMENT ON COLUMN insight.category IS
    'Свободная категория (рекоменд.: competition|permitting|demand|risk|other).';
COMMENT ON COLUMN insight.geom IS
    'Опциональный map-pin, GENERATED из lon/lat. NULL если координаты не заданы.';

-- ── 2. CHECK на category (guarded — idempotent при ре-apply) ──────────────────
-- Мягкий enum: разрешаем рекомендованные значения + NULL. Расширять список —
-- через новую миграцию (DROP IF EXISTS + ADD), не правкой этой.
DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint WHERE conname = 'ck_insight_category'
    ) THEN
        ALTER TABLE insight
            ADD CONSTRAINT ck_insight_category
            CHECK (
                category IS NULL
                OR category IN ('competition', 'permitting', 'demand', 'risk', 'other')
            );
    END IF;
END
$$;

-- ── 3. Индексы: фильтры list-эндпоинта ────────────────────────────────────────
CREATE INDEX IF NOT EXISTS idx_insight_district
    ON insight (district);
CREATE INDEX IF NOT EXISTS idx_insight_cad_num
    ON insight (cad_num);
CREATE INDEX IF NOT EXISTS idx_insight_is_confidential
    ON insight (is_confidential);
CREATE INDEX IF NOT EXISTS idx_insight_created_by
    ON insight (created_by);
-- Опциональный geo-lookup (как user_custom_pois_geom_gist #254).
CREATE INDEX IF NOT EXISTS idx_insight_geom_gist
    ON insight USING GIST (geom);

-- ── 4. Расширяем CHECK audit_log.action → +'insight_write' (§19, #962) ─────────
-- Middleware аудирует WRITE insight'ов как action='insight_write'. 144_audit_log
-- разрешал только analyze|forecast|export|other; добавляем 'insight_write'.
-- Idempotent: DROP IF EXISTS + ADD пере-создаёт констрейнт с расширенным набором
-- (повторный apply этой миграции даст тот же результат). Таблица audit_log создана
-- в 144 (она в HEAD/проде до этой миграции) — но guard'имся to_regclass на случай
-- среды, где 144 ещё не накатан, чтобы 145 не падал.
DO $$
BEGIN
    IF to_regclass('public.audit_log') IS NOT NULL THEN
        ALTER TABLE audit_log DROP CONSTRAINT IF EXISTS ck_audit_log_action;
        ALTER TABLE audit_log
            ADD CONSTRAINT ck_audit_log_action
            CHECK (action IN ('analyze', 'forecast', 'export', 'insight_write', 'other'));
    END IF;
END
$$;

COMMIT;
