-- 80_complexes_backfill_nulls.sql
-- Quick-win backfill для public.complexes через spatial joins + lookups.
-- Закрывает NULL'ы без новых scrapers.
--
-- Что делает:
--   A. cad_quarter    — spatial join ST_Contains(cad_quarters_geom.geom, complexes.geom)
--   B. cad_building_num — колонка 100% NULL, никто не пишет в неё.
--                         Выбрана Опция A: колонка дропается, relation живёт через
--                         cad_buildings.complex_id (FK 1:N, уже существует).
--                         Создаётся VIEW v_complex_buildings.
--                         v_complex_full пересоздаётся без cad_building_num + с
--                         cad_building_nums TEXT[] из агрегации по cad_buildings.
--   C. district_name  — spatial join ST_Contains(ekb_districts.geom, complexes.geom)
--                         (только EKB-покрытие, ~9 районов)
--   D. developer_id   — fuzzy match lower(trim(developer_name)) с domrf_developers.
--                         Ambiguous (>1 match по имени) — пропускаем, берём только
--                         уникальные совпадения.
--   E. obj_class      — источник данных domrf_kn_objects.obj_class = NULL для
--                         всех 4548 строк; objective_lots.class даёт только 2 complexes.
--                         Шаг выполняется (закрывает эти 2), но большого эффекта нет.
--   F. wall_type      — domrf_kn_objects.wall_type = NULL для всех строк;
--                         objective_lots не содержит wall_type.
--                         Источника данных нет — шаг пропускается.
--
-- Dependencies:
--   - public.complexes          (таблица)
--   - public.cad_quarters_geom  (GIST индекс cad_quarters_geom_gist)
--   - public.ekb_districts      (GIST индекс ekb_districts_geom_idx)
--   - public.domrf_developers   (btree на developer_id PK)
--   - public.objective_lots     (FK complex_id, поле class)
--   - public.cad_buildings      (FK complex_id)
--   - public.v_complex_full     (пересоздаётся — DROP CASCADE + CREATE)
--
-- Apply order: после 78_drop_use_rosreestr2coord.sql
--
-- Идемпотентность:
--   - UPDATE ... WHERE column IS NULL — повторный запуск не перезапишет NOT NULL.
--   - DROP TABLE IF EXISTS / CREATE OR REPLACE VIEW — безопасны.
--   - DROP COLUMN IF EXISTS — безопасен.

BEGIN;

-- ============================================================
-- A. cad_quarter — spatial join
-- ============================================================
-- Spatial indexes используются (EXPLAIN: Index Scan on cad_quarters_geom).
-- LIMIT 1 защищает от boundary случаев когда точка лежит на двух кварталах.
UPDATE complexes c
SET cad_quarter = (
    SELECT q.cad_number
    FROM cad_quarters_geom q
    WHERE ST_Contains(q.geom, c.geom)
    LIMIT 1
)
WHERE c.cad_quarter IS NULL AND c.geom IS NOT NULL;

-- ============================================================
-- B. cad_building_num — Опция A: дропаем колонку, relation через cad_buildings.complex_id
-- ============================================================

-- Шаг B.1: пересоздать v_complex_full без cad_building_num.
-- Вместо неё добавляем cad_building_nums TEXT[] (агрегация из cad_buildings).
-- DROP CASCADE нужен потому что PostgreSQL не позволяет CREATE OR REPLACE VIEW
-- если изменяется список/порядок колонок (cad_building_num убираем, cad_building_nums добавляем).
DROP VIEW IF EXISTS v_complex_full CASCADE;

CREATE OR REPLACE VIEW v_complex_full AS
SELECT
    c.id                        AS complex_id,
    c.canonical_name,
    c.developer_name,
    c.region_id,
    c.district_name,
    c.obj_class,
    c.address,
    c.latitude,
    c.longitude,
    (c.geom IS NOT NULL)        AS has_geom,
    c.flat_count,
    c.square_living,
    c.site_status,
    c.cad_quarter,
    -- cad_building_nums заменяет cad_building_num: агрегат из cad_buildings.cad_num
    (
        SELECT array_agg(b.cad_num ORDER BY b.cad_num)
        FROM cad_buildings b
        WHERE b.complex_id = c.id
    )                           AS cad_building_nums,
    (
        SELECT COUNT(*)
        FROM cad_buildings b
        WHERE b.complex_id = c.id
    )                           AS cad_buildings_n,
    (
        SELECT array_agg(DISTINCT cs.source ORDER BY cs.source)
        FROM complex_sources cs
        WHERE cs.complex_id = c.id
    )                           AS sources,
    (
        SELECT COUNT(*)
        FROM complex_sources cs
        WHERE cs.complex_id = c.id
    )                           AS source_links_n,
    (
        SELECT COUNT(DISTINCT cs.source)
        FROM complex_sources cs
        WHERE cs.complex_id = c.id
    )                           AS source_kinds_n,
    (
        SELECT array_agg(cs.source_external_id ORDER BY cs.source_external_id)
        FROM complex_sources cs
        WHERE cs.complex_id = c.id
          AND cs.source = 'domrf_kn'
    )                           AS domrf_obj_ids,
    (
        SELECT cs.source_id
        FROM complex_sources cs
        WHERE cs.complex_id = c.id
          AND cs.source = 'objective'
        LIMIT 1
    )                           AS objective_project_name,
    (
        SELECT COUNT(*)
        FROM domrf_kn_flats f
        WHERE f.obj_id IN (
            SELECT cs.source_external_id
            FROM complex_sources cs
            WHERE cs.complex_id = c.id
              AND cs.source = 'domrf_kn'
              AND cs.source_external_id IS NOT NULL
        )
    )                           AS kn_flats_n,
    (
        SELECT COUNT(*)
        FROM objective_lots ol
        WHERE ol.complex_id = c.id
    )                           AS objective_lots_n,
    c.primary_source,
    c.created_at,
    c.updated_at
FROM complexes c;

-- Шаг B.2: дропаем колонку cad_building_num (100% NULL, индекс дропается автоматически)
ALTER TABLE complexes DROP COLUMN IF EXISTS cad_building_num;

-- Шаг B.3: VIEW v_complex_buildings — удобный агрегат complex → buildings
CREATE OR REPLACE VIEW v_complex_buildings AS
SELECT
    c.id                                                    AS complex_id,
    c.canonical_name,
    array_agg(DISTINCT b.cad_num ORDER BY b.cad_num)       AS cad_building_nums,
    COUNT(b.cad_num)                                        AS buildings_count
FROM complexes c
LEFT JOIN cad_buildings b ON b.complex_id = c.id
GROUP BY c.id, c.canonical_name;

-- ============================================================
-- C. district_name — spatial join (EKB coverage only)
-- ============================================================
-- EXPLAIN: Index Scan on ekb_districts_geom_idx — GIST index используется.
-- Закрывает только complexes внутри 9 районов EKB (geom IS NOT NULL).
-- Complexes без geom или за пределами EKB остаются NULL.
UPDATE complexes c
SET district_name = (
    SELECT d.district_name
    FROM ekb_districts d
    WHERE d.geom IS NOT NULL
      AND ST_Contains(d.geom, c.geom)
    LIMIT 1
)
WHERE c.district_name IS NULL AND c.geom IS NOT NULL;

-- ============================================================
-- D. developer_id — fuzzy name match с domrf_developers
-- ============================================================
-- Берём только случаи с ровно одним совпадением по нормализованному имени
-- (lower + trim). Ambiguous (29 complexes с ≥2 candidates) — пропускаем.
UPDATE complexes c
SET developer_id = (
    SELECT d.developer_id
    FROM domrf_developers d
    WHERE lower(trim(d.developer_name)) = lower(trim(c.developer_name))
    LIMIT 1
)
WHERE c.developer_id IS NULL
  AND c.developer_name IS NOT NULL
  AND (
      -- только если ровно одна запись в domrf_developers с таким именем
      SELECT COUNT(DISTINCT d2.developer_id)
      FROM domrf_developers d2
      WHERE lower(trim(d2.developer_name)) = lower(trim(c.developer_name))
  ) = 1;

-- ============================================================
-- E. obj_class — via objective_lots.class (2 complexes покрывается)
-- ============================================================
-- domrf_kn_objects.obj_class = NULL для всех строк (источник не заполнен).
-- objective_lots.class — единственный источник; покрывает ~2 complexes с NULL obj_class.
-- Берём mode (наиболее частый класс) по complex_id.
UPDATE complexes c
SET obj_class = (
    SELECT ol.class
    FROM objective_lots ol
    WHERE ol.complex_id = c.id
      AND ol.class IS NOT NULL
    GROUP BY ol.class
    ORDER BY COUNT(*) DESC
    LIMIT 1
)
WHERE c.obj_class IS NULL
  AND EXISTS (
      SELECT 1 FROM objective_lots ol2
      WHERE ol2.complex_id = c.id AND ol2.class IS NOT NULL
  );

-- ============================================================
-- F. wall_type — источника данных нет
-- ============================================================
-- domrf_kn_objects.wall_type = NULL для всех 4548 строк.
-- objective_lots не содержит wall_type.
-- Шаг пропускается — нет источника для backfill.

COMMIT;
