"""Пин SQL-формы competitors._AVG_PRICE_SQL (эпик #2445, пункт A5).

Byte-identical бага к best_layouts._SUPPLY_BATCH_SQL (#1956): domrf_kn_flats —
ПО-ОБЪЕКТНЫЙ time-series (каждый ЖК скрейпится в свой день, UNIQUE(obj_id,
snapshot_date), migration 174_domrf_kn_flats_obj_snapshot_idx.sql). Прежний
запрос фильтровал по ОДНОЙ глобальной дате
`f.snapshot_date = (SELECT MAX(snapshot_date) FROM domrf_kn_flats)` — на этой
единственной дате обычно присутствует лишь 1 объект (скрейпнутый последним),
поэтому у ВСЕХ остальных конкурентов в радиусе получалось 0 строк flats →
avg_price_per_m2 = NULL → _price_similarity(None, ...) молча возвращал
нейтраль 0.5, тихо деградируя relevance-ранжирование без единого флага.

FIX (порт #1956 в этот файл): flats_latest CTE — DISTINCT ON (f.obj_id) ...
ORDER BY f.obj_id, snapshot_date DESC, id DESC — берёт для КАЖДОГО obj_id его
СОБСТВЕННЫЙ последний снимок, затем self-join к domrf_kn_flats по
(obj_id, snapshot_date) достаёт квартиры именно этого снимка.

Эти тесты — строковые инварианты на скомпилированном SQL (без живой БД), чтобы
будущий рефактор случайно не вернул глобал-max-date регрессию.
"""

from __future__ import annotations

import os
import re

os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")

from app.services.site_finder.competitors import _AVG_PRICE_SQL

_SQL = str(_AVG_PRICE_SQL)
_SQL_FLAT = re.sub(r"\s+", " ", _SQL).lower()


def test_avg_price_uses_flats_latest_per_object_cte() -> None:
    """flats_latest CTE обязателен — без него avg_price снова считается на
    единственной глобал-max дате (регрессия #2445 A5, byte-identical к #1956)."""
    assert "flats_latest" in _SQL_FLAT, "нет flats_latest CTE → возврат к global-max-date багу"
    assert "distinct on (f.obj_id)" in _SQL_FLAT, "flats не дедупятся per-object"


def test_avg_price_orders_latest_snapshot_first() -> None:
    """ORDER BY последним снапшотом вперёд (snapshot_date DESC, id DESC) — DISTINCT ON
    берёт первую строку группы → собственный последний снимок объекта."""
    assert "snapshot_date desc" in _SQL_FLAT
    assert "id desc" in _SQL_FLAT
    m = re.search(r"order by f\.obj_id, f\.snapshot_date desc, f\.id desc", _SQL_FLAT)
    assert m is not None, "DISTINCT ON должен сортировать per obj_id, затем latest snapshot"


def test_avg_price_no_global_max_snapshot_date() -> None:
    """Регрессия #2445 A5: единственный глобальный MAX(snapshot_date) по всей
    таблице не должен вернуться — это и есть исходный баг (1 объект на весь
    global-max день, остальные competitors получают avg_price=NULL)."""
    assert "select max(snapshot_date) from domrf_kn_flats" not in _SQL_FLAT
    assert "(select max(snapshot_date)" not in _SQL_FLAT


def test_avg_price_joins_flats_by_obj_id_and_snapshot_date() -> None:
    """Self-join к domrf_kn_flats обязан фильтровать И по obj_id, И по
    snapshot_date дедуплицированного flats_latest — иначе снова fan-out по
    истории снимков объекта (см. #1210)."""
    assert re.search(r"join\s+domrf_kn_flats\s+f\b", _SQL_FLAT)
    assert "f.obj_id = fl.obj_id" in _SQL_FLAT
    assert "f.snapshot_date = fl.snapshot_date" in _SQL_FLAT


def test_avg_price_keeps_price_not_null_filter() -> None:
    """AVG(...) FILTER (WHERE price_per_m2 IS NOT NULL) сохранён (Issue #112/227
    — status='sold' фильтр убран, но NULL-цены по-прежнему исключаются из AVG)."""
    assert "avg(f.price_per_m2) filter (where f.price_per_m2 is not null)" in _SQL_FLAT


def test_avg_price_no_double_colon_cast() -> None:
    """psycopg v3 / backend.md: никаких :name::type (игнорируется парсером)."""
    assert not re.search(r":[a-z_]+::[a-z]", _SQL)
