-- 193_trade_in_privacy_retention.sql
-- ЭТАП 4 B2C launch — retention enforcement (152-ФЗ): срок хранения ДОЛЖЕН
-- приводить к физическому удалению, а не быть декоративным полем.
--
-- WHY:
--   trade_in_estimates.expires_at существовал (migration 004), но применялся
--   ТОЛЬКО как read-time фильтр (GET /estimate/{id}: "AND expires_at > NOW()")
--   — ни одна фоновая задача не удаляла строки после истечения TTL. Данные
--   физлица (адрес) жили в БД бессрочно вопреки декларированному сроку.
--   trade_in_leads было ещё хуже: там вообще НЕ было понятия TTL/expires_at —
--   телефон + согласие хранились бессрочно с момента создания таблицы
--   (172_trade_in_leads.sql).
--
-- WHAT:
--   1. trade_in_leads.expires_at (timestamptz NOT NULL) — backfill для
--      существующих строк = created_at + 180 дней (тот же default, что
--      settings.trade_in_lead_retention_days в app/core/config.py на момент
--      этой миграции; 180 дней = рабочий MVP-default для НЕконвертированных
--      маркетинговых лидов, см. обоснование в config.py — конкретный
--      юридически обоснованный срок хранения это решение DPO/юриста, не
--      инженера). Новые строки получают expires_at на insert-time
--      (app/api/v1/lead.py, тем же паттерном, что trade_in_estimates).
--   2. Индекс на trade_in_leads.expires_at — для batched-DELETE ниже.
--   3. scrape_schedules seed: purge_expired_trade_in_data — ночная задача
--      (app/tasks/purge_expired_trade_in_data.py, kit-handler в
--      app/services/product_handlers.py), физически удаляющая ИСТЁКШИЕ
--      строки в trade_in_estimates И trade_in_leads пачками (batch_size из
--      default_params, лимит max_batches за один прогон — см. таск-докстринг).
--      ON DELETE CASCADE (007_estimate_photos, 018_avito_imv_evaluations) и
--      ON DELETE SET NULL (172_trade_in_leads.estimate_id) уже подчищают
--      зависимые таблицы автоматически — этот файл их не трогает.
--
--   Seeded с enabled=false (тот же осторожный паттерн, что
--   175_scrape_schedules_seed_domclick_detail_backfill.sql): это ПЕРВАЯ
--   автоматическая задача физического DELETE персональных данных в trade-in —
--   заслуживает supervised первого прогона (смотри логи/counters вручную)
--   перед тем, как доверить её расписанию. Включение — отдельный ручной шаг
--   (UPDATE scrape_schedules SET enabled=true WHERE source=
--   'purge_expired_trade_in_data').
--
-- IDEMPOTENCY / SAFETY:
--   - ADD COLUMN IF NOT EXISTS + UPDATE ... WHERE expires_at IS NULL (no-op на
--     повторном прогоне, все строки уже проставлены) +
--     ALTER COLUMN ... SET NOT NULL (идемпотентно само по себе — Postgres не
--     ошибается на повторной установке уже действующего NOT NULL).
--   - CREATE INDEX IF NOT EXISTS — безопасный re-run.
--   - INSERT ... ON CONFLICT (source) DO NOTHING — безопасный re-run seed'а.
--
-- Dependencies: 172_trade_in_leads.sql (таблица), 052_scrape_schedules.sql
--   (scrape_schedules), 192_trade_in_estimates_consent_proof.sql (соседняя
--   часть той же ЭТАП 4 инициативы — индекс на trade_in_estimates.expires_at
--   уже создан там).

BEGIN;

ALTER TABLE trade_in_leads
    ADD COLUMN IF NOT EXISTS expires_at timestamptz;

UPDATE trade_in_leads
SET expires_at = created_at + interval '180 days'
WHERE expires_at IS NULL;

ALTER TABLE trade_in_leads
    ALTER COLUMN expires_at SET NOT NULL;

CREATE INDEX IF NOT EXISTS trade_in_leads_expires_at_idx
    ON trade_in_leads (expires_at);

COMMENT ON COLUMN trade_in_leads.expires_at IS 'ЭТАП 4 B2C (152-ФЗ): срок хранения лида. Backfill для legacy-строк = created_at + 180 дней; новые строки считаются на insert-time из settings.trade_in_lead_retention_days (app/api/v1/lead.py). Физическое удаление после истечения — app/tasks/purge_expired_trade_in_data.py.';

INSERT INTO scrape_schedules (
    source,
    enabled,
    window_start_hour,
    window_end_hour,
    next_run_at,
    default_params
)
VALUES
(
    'purge_expired_trade_in_data',
    false,
    2,
    4,
    ((CURRENT_DATE + INTERVAL '1 day') + make_interval(hours => 2)) AT TIME ZONE 'UTC',
    '{"batch_size": 500, "max_batches": 20}'::jsonb
)
ON CONFLICT (source) DO NOTHING;

COMMENT ON TABLE scrape_schedules IS
    'In-app scheduler config (replaces cron-script setup). Sources: avito_city_sweep, yandex_city_sweep (dormant, #561), cian_history_backfill, rosreestr_dkp_import, listing_source_snapshot (#570), asking_to_sold_ratio_refresh (#648), refresh_search_matview (#769), yandex_address_backfill (#855, EKB pilot), sber_index_pull (#887, monthly), rosreestr_quarter_poll (#888, monthly), cian_city_sweep (dormant, #973), yandex_newbuilding_sweep (dormant, #974), geocode_missing_listings (#1: listings geom backfill, all sources), avito_detail_backfill (#1551: nightly detail-enrichment backfill for legacy avito listings), domclick_detail_backfill (#2000: nightly Layer B detail-enrichment backfill for domklik listings, cookie-injection + QRATOR-aware, disabled by default until smoke-tested), purge_expired_trade_in_data (ЭТАП 4 B2C: nightly batched physical DELETE of expired trade_in_estimates/trade_in_leads rows, disabled by default until a supervised first run).';

COMMIT;
