-- Trade-In MVP — Core data layer (Слой 1.1).
-- Применяется автоматически после 001_trade_in_estimates.sql при первом старте postgres.

BEGIN;

-- ── PostGIS для гео-запросов ──────────────────────────────────────────────
CREATE EXTENSION IF NOT EXISTS postgis;


-- ─────────────────────────────────────────────────────────────────────────
-- listings — спарсенные объявления (Авито, Циан, ДомКлик, Я.Нед, Н1, Дом.РФ)
-- ─────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS listings (
    id              bigserial PRIMARY KEY,
    source          text NOT NULL,                 -- avito / cian / domklik / yandex / n1 / domrf
    source_url      text NOT NULL,
    source_id       text,                          -- внутренний id источника (если есть)
    dedup_hash      text NOT NULL UNIQUE,          -- sha256(source + source_url + price) — дедуп

    -- Локация
    address         text,
    lat             double precision,
    lon             double precision,
    geom            geometry(Point, 4326),         -- PostGIS для ST_DWithin
    region_code     int,                           -- 66 = Свердловская обл.

    -- Параметры квартиры
    rooms           int,                           -- 0 = студия
    area_m2         numeric(8, 2),
    floor           int,
    total_floors    int,
    year_built      int,
    house_type      text,                          -- panel / brick / monolith / monolith_brick / other
    repair_state    text,                          -- needs_repair / standard / good / excellent
    has_balcony     boolean,
    kadastr_num     text,                          -- если удалось вытянуть кадастр

    -- Цена
    price_rub       bigint NOT NULL,
    price_per_m2    int,

    -- Метаданные источника
    listing_date    date,                          -- когда выставили
    days_on_market  int,
    photo_urls      jsonb DEFAULT '[]'::jsonb,
    raw_payload     jsonb,                         -- сырой ответ для дебага

    -- Quality flags
    is_outlier      boolean DEFAULT false,         -- помечено модели после aggregation
    is_active       boolean DEFAULT true,          -- если объявление пропало с источника

    -- Lifecycle
    scraped_at      timestamptz NOT NULL DEFAULT NOW(),
    last_seen_at    timestamptz NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS listings_geom_idx       ON listings USING GIST (geom);
CREATE INDEX IF NOT EXISTS listings_rooms_area_idx ON listings (rooms, area_m2) WHERE is_active = true;
CREATE INDEX IF NOT EXISTS listings_scraped_idx    ON listings (scraped_at DESC) WHERE is_active = true;
CREATE INDEX IF NOT EXISTS listings_source_idx     ON listings (source, scraped_at DESC);

-- Авто-update geom из (lat, lon)
CREATE OR REPLACE FUNCTION listings_set_geom() RETURNS trigger AS $$
BEGIN
    IF NEW.lat IS NOT NULL AND NEW.lon IS NOT NULL THEN
        NEW.geom := ST_SetSRID(ST_MakePoint(NEW.lon, NEW.lat), 4326);
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS listings_set_geom_trg ON listings;
CREATE TRIGGER listings_set_geom_trg
    BEFORE INSERT OR UPDATE OF lat, lon ON listings
    FOR EACH ROW EXECUTE FUNCTION listings_set_geom();

COMMENT ON TABLE listings IS
    'Спарсенные объявления с площадок. Дедуп по dedup_hash. PostGIS geom для ST_DWithin запросов.';


-- ─────────────────────────────────────────────────────────────────────────
-- deals — фактические сделки (Росреестр + Этажи + ДомКлик исторические)
-- ─────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS deals (
    id              bigserial PRIMARY KEY,
    source          text NOT NULL,                 -- rosreestr / etazhi / domklik_history
    source_id       text,                          -- идентификатор сделки в источнике
    dedup_hash      text NOT NULL UNIQUE,

    -- Локация
    address         text,
    lat             double precision,
    lon             double precision,
    geom            geometry(Point, 4326),
    region_code     int,
    kadastr_num     text,

    -- Параметры квартиры
    rooms           int,
    area_m2         numeric(8, 2),
    floor           int,
    total_floors    int,
    year_built      int,
    house_type      text,

    -- Сделка
    deal_date       date NOT NULL,
    price_rub       bigint NOT NULL,
    price_per_m2    int,
    days_on_market  int,                           -- если знаем когда выставили

    raw_payload     jsonb,
    scraped_at      timestamptz NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS deals_geom_idx       ON deals USING GIST (geom);
CREATE INDEX IF NOT EXISTS deals_rooms_area_idx ON deals (rooms, area_m2);
CREATE INDEX IF NOT EXISTS deals_date_idx       ON deals (deal_date DESC);
CREATE INDEX IF NOT EXISTS deals_source_idx     ON deals (source, deal_date DESC);

DROP TRIGGER IF EXISTS deals_set_geom_trg ON deals;
CREATE TRIGGER deals_set_geom_trg
    BEFORE INSERT OR UPDATE OF lat, lon ON deals
    FOR EACH ROW EXECUTE FUNCTION listings_set_geom();  -- reuse

COMMENT ON TABLE deals IS
    'Фактические сделки купли-продажи квартир. Источник #1 — Росреестр (дамп из prod gendesign).';


-- ─────────────────────────────────────────────────────────────────────────
-- geocode_cache — кэш geocoder (Nominatim/Yandex)
-- ─────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS geocode_cache (
    address_normalized  text PRIMARY KEY,          -- lowercased trimmed адрес для lookup
    lat                 double precision NOT NULL,
    lon                 double precision NOT NULL,
    full_address        text,                      -- что вернул geocoder
    provider            text NOT NULL,             -- nominatim / yandex
    confidence          text,                      -- exact / approximate / locality
    raw_payload         jsonb,
    created_at          timestamptz NOT NULL DEFAULT NOW(),
    -- TTL: 90 дней — потом стоит проверить актуальность (дома сносят редко, но бывает)
    expires_at          timestamptz NOT NULL DEFAULT NOW() + interval '90 days'
);

CREATE INDEX IF NOT EXISTS geocode_cache_expires_idx ON geocode_cache (expires_at);

COMMENT ON TABLE geocode_cache IS
    'Кэш geocoder lookup. Адрес → lat/lon. TTL 90 дней.';


-- ─────────────────────────────────────────────────────────────────────────
-- house_metadata — кэш данных ЕКБ Геопортала (citymap.ekburg.ru)
-- ─────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS house_metadata (
    id              bigserial PRIMARY KEY,
    lat             double precision NOT NULL,
    lon             double precision NOT NULL,
    geom            geometry(Point, 4326),
    kadastr_num     text UNIQUE,

    -- Что вытянули из ЕКБ Геопортала / DomRF / NSPD
    address         text,
    total_floors    int,
    year_built      int,
    house_type      text,
    total_units     int,                           -- кол-во квартир в доме
    construction_status text,                      -- сдан / строится
    developer       text,                          -- застройщик если новостройка

    source          text,                          -- ekb_geoportal / domrf / nspd
    raw_payload     jsonb,
    fetched_at      timestamptz NOT NULL DEFAULT NOW(),
    -- TTL: 30 дней
    expires_at      timestamptz NOT NULL DEFAULT NOW() + interval '30 days'
);

CREATE INDEX IF NOT EXISTS house_metadata_geom_idx    ON house_metadata USING GIST (geom);
CREATE INDEX IF NOT EXISTS house_metadata_expires_idx ON house_metadata (expires_at);

DROP TRIGGER IF EXISTS house_metadata_set_geom_trg ON house_metadata;
CREATE TRIGGER house_metadata_set_geom_trg
    BEFORE INSERT OR UPDATE OF lat, lon ON house_metadata
    FOR EACH ROW EXECUTE FUNCTION listings_set_geom();

COMMENT ON TABLE house_metadata IS
    'Кэш метаданных дома по координатам/кадастру (ЕКБ Геопортал, DomRF, NSPD).';


-- ─────────────────────────────────────────────────────────────────────────
-- audit_log — каждый POST /estimate и GET /estimate/{id}/pdf
-- ─────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS audit_log (
    id              bigserial PRIMARY KEY,
    event_type      text NOT NULL,                 -- estimate_request / pdf_download / listing_click
    ip_address      inet,
    user_agent      text,
    estimate_id     uuid,                          -- FK к trade_in_estimates если применимо
    payload         jsonb,                         -- input/output для контекста
    created_at      timestamptz NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS audit_log_created_idx    ON audit_log (created_at DESC);
CREATE INDEX IF NOT EXISTS audit_log_event_type_idx ON audit_log (event_type, created_at DESC);

COMMENT ON TABLE audit_log IS
    'Лог всех значимых событий — для analytics dashboard + rate limiting + debugging.';


-- ─────────────────────────────────────────────────────────────────────────
-- brands — multi-tenant white-label
-- ─────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS brands (
    slug            text PRIMARY KEY,              -- prinzip / praktika / generic
    name            text NOT NULL,
    logo_url        text,
    primary_color   text DEFAULT '#1d4ed8',
    accent_color    text DEFAULT '#f59e0b',
    footer_text     text,
    pdf_disclaimer  text,
    created_at      timestamptz NOT NULL DEFAULT NOW()
);

INSERT INTO brands (slug, name, primary_color) VALUES
    ('generic',  'Trade-In Estimator', '#1d4ed8'),
    ('prinzip',  'PRINZIP',            '#1a1d23'),
    ('praktika', 'Практика',           '#e87722')
ON CONFLICT (slug) DO NOTHING;

COMMENT ON TABLE brands IS
    'White-label бренды. Выбирается через ?brand=<slug> на frontend.';


COMMIT;
