-- 164_yandex_url_canonicalize_active_dups.sql
-- Issue #2235 — yandex: 204 «активных URL-дубля» среди is_active строк listings
-- (один source_url делит >1 активной строки).
--
-- ROOT CAUSE (проверено на локальной БД tradein 2026-07-03, SELECT-only):
--   Все 204 группы дублей — yandex listing_segment='novostroyki', суммарно 1253
--   активные строки. В КАЖДОЙ группе source_id РАЗНЫЕ (nid=n, 0 NULL), цены/квартиры
--   разные — это ГЕНУИННО РАЗНЫЕ офферы, а НЕ дубли одной строки. dedup_hash =
--   sha256(source|source_id) уникален на каждый оффер, row-level дедуп работает
--   корректно. Дублируется ТОЛЬКО значение source_url.
--
--   Причина: для новостроек gate-API yandex возвращает в поле `url` НЕканоническую
--   ссылку — сайт застройщика / рекламный редирект (na100.pro/go.php?link=…, 732
--   строки; atlasdevelopment.su/ — 279 на 1 URL; unistroyrf.ru/ — 127 на 1;
--   vtmn.ru/complexes/… — 74 на 1; и т.п.). _entity_to_lot клал этот partner-URL
--   в source_url как есть. МНОГО разных квартир одного ЖК делят ОДНУ посадочную
--   страницу → 204 общих source_url. Вторичка (4193 строки) несёт корректный
--   //realty.yandex.ru/offer/<id> и дублей не даёт.
--
-- ПОЧЕМУ НЕ «СХЛОПЫВАНИЕ» (вариант из issue отклонён по данным):
--   Схлопнуть группу на свежайшую строку и погасить остальные (is_active=false)
--   было бы ДЕСТРУКТИВНО: удалило бы ~1049 реальных активных новостроечных
--   офферов из инвентаря (coverage/estimator/UI). Это не дубли-строки, а разные
--   объекты. Вместо этого КАНОНИЗИРУЕМ source_url: каждому офферу его собственный
--   стабильный per-offer URL из source_id → «дубль URL» исчезает БЕЗ потери строк.
--
-- ПОЧЕМУ НЕ partial UNIQUE index ON (source, source_url) WHERE is_active
--   (вариант Б из issue — уникальность в ETL, индекс НЕ добавляем):
--   1) Конфликт-арбитр апсерта save_listings — dedup_hash (sha256(source|source_id)),
--      НЕ source_url; reconcile-SAVEPOINT ловит только UniqueViolation по
--      (source, source_id). UNIQUE по (source, source_url) фаерил бы на INSERT
--      ВТОРОГО распознанного оффера того же ЖК с общей posadka-страницей — путь
--      апсерта его НЕ реконсайлит → падение всего скрейп-лота.
--      (app/services/scrapers/base.py:455 ON CONFLICT (dedup_hash);
--       reconcile UPDATE by (source, source_id) на base.py:564-628.)
--   2) Разные офферы легитимно могут делить не-yandex URL — индекс запрещал бы
--      это как «дубль», хотя канонический ключ оффера = source_id (constraint
--      133_listings_uq_source_source_id.sql), а не URL.
--   Инвариант вместо индекса: канонизация URL в yandex-скрейпере
--   (app/services/scrapers/yandex_realty.py + scraper-kit serp.py) + юнит-тест.
--
-- WHAT (эта миграция):
--   Для каждой активной yandex-строки, чей source_url делится >1 строкой
--   (искомые «дубли»), переписываем source_url на канонический
--   'https://realty.yandex.ru/offer/' || source_id || '/'. Строки НЕ гасим —
--   остаются is_active=true. source_id внутри группы различны (проверено) и
--   глобально уникальны (constraint 133) → канонические URL различны → группа
--   рассыпается, дублей не остаётся. Коллизий с существующими активными
--   realty.yandex-URL нет (проверено: 0).
--
-- IDEMPOTENCY / SAFETY (важно для прода — deploy-tradein.yml применяет строго,
--   ON_ERROR_STOP):
--   - BEGIN/COMMIT — атомарно.
--   - Non-destructive: меняется ТОЛЬКО значение source_url, is_active не трогаем.
--   - Повторный прогон = no-op: после первого раза каждый оффер несёт свой
--     уникальный канонический URL → CTE `shared` (source_url с count>1) пуст →
--     UPDATE матчит 0 строк. Плюс страховочный guard `source_url <> <canonical>`
--     пропускает уже-канонические строки.
--   - Только source_id ~ '^[0-9]+$' (числовой yandex offerId) переписывается;
--     source_id IS NULL/нечисловой не трогаем.
--
-- Ожидаемый объём ~1253 строки (быстрый single UPDATE, is_active индексируем).
--
-- DEPENDENCIES: listings (source, source_url, source_id, is_active).

BEGIN;

WITH shared AS (
    -- Активные yandex source_url, которые делит >1 строка = искомые «дубли».
    SELECT source_url
      FROM listings
     WHERE is_active
       AND source = 'yandex'
       AND source_url IS NOT NULL
     GROUP BY source_url
    HAVING count(*) > 1
)
UPDATE listings l
   SET source_url = 'https://realty.yandex.ru/offer/' || l.source_id || '/'
  FROM shared s
 WHERE l.is_active
   AND l.source = 'yandex'
   AND l.source_url = s.source_url
   AND l.source_id IS NOT NULL
   AND l.source_id ~ '^[0-9]+$'
   AND l.source_url <> 'https://realty.yandex.ru/offer/' || l.source_id || '/';

COMMIT;
