"""Integration test for Issue #99 — ДДУ price indicator SQL logic.

Builds a synthetic temp table mirroring rosreestr_deals and runs the exact
bucketing + index computation from migration 152 / ddu_price_indicator against
a real PostgreSQL. Proves:

1. Per-unit area bucketing uses area/deal_count (packaged ДДУ), not raw area.
2. index_basis is median / first-period-median; index_previous is median /
   previous-PRESENT-period median (min_deals=10 gates noisy quarters out).
3. prev_period_value honestly records which period index_previous compares to
   (may NOT be the literal previous quarter if one was filtered out).

psycopg v3 only. CAST(:x AS type) everywhere. Skips cleanly off-CI.
"""

import os
from decimal import Decimal

import psycopg
import pytest


def _get_dsn() -> str:
    raw = os.environ.get("TEST_DATABASE_URL") or os.environ.get(
        "DATABASE_URL",
        "postgresql://gendesign@localhost:15432/gendesign",
    )
    return raw.replace("+psycopg", "")


def _db_reachable() -> tuple[bool, str]:
    try:
        with psycopg.connect(_get_dsn(), connect_timeout=3):
            return True, ""
    except Exception as e:
        return False, str(e)


_DB_OK, _DB_ERR = _db_reachable()
pytestmark = pytest.mark.skipif(
    not _DB_OK,
    reason=(
        "Нет доступной postgres БД (TEST_DATABASE_URL/DATABASE_URL) — "
        f"тест #99 пропущен: {_DB_ERR}"
    ),
)


# Core indicator query under test — mirrors migration 152 (reads temp table rd).
_INDICATOR_SQL = """
WITH per_unit AS (
    SELECT date_trunc('quarter', period_start_date)::date AS quarter_start,
           (area / deal_count) AS area_per_unit,
           CASE WHEN price_per_sqm IS NOT NULL THEN price_per_sqm
                WHEN deal_price IS NOT NULL AND area > 0 THEN deal_price/area END AS price_m2
      FROM rd
     WHERE region_code = 66 AND realestate_type_code = '002001003000'
       AND doc_type = 'ДДУ' AND deal_count > 0 AND area > 0
       AND period_start_date >= DATE '2025-04-01'
),
filtered AS (
    SELECT quarter_start, area_per_unit, price_m2 FROM per_unit
     WHERE area_per_unit BETWEEN 10 AND 300 AND price_m2 BETWEEN 30000 AND 800000
),
bucketed AS (
    SELECT quarter_start, price_m2,
           CASE WHEN area_per_unit < 25 THEN 1 WHEN area_per_unit < 40 THEN 2
                WHEN area_per_unit < 60 THEN 3 WHEN area_per_unit < 80 THEN 4
                WHEN area_per_unit < 100 THEN 5 ELSE 6 END AS area_bucket
      FROM filtered
    UNION ALL
    SELECT quarter_start, price_m2, 0 FROM filtered
),
agg AS (
    SELECT area_bucket, quarter_start, COUNT(*) AS deals_count,
           PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price_m2) AS med
      FROM bucketed GROUP BY area_bucket, quarter_start
    HAVING COUNT(*) >= 10
),
indexed AS (
    SELECT area_bucket, quarter_start, deals_count, med,
           FIRST_VALUE(med) OVER w AS basis_med,
           LAG(med) OVER w AS prev_med,
           LAG(quarter_start) OVER w AS prev_q
      FROM agg WINDOW w AS (PARTITION BY area_bucket ORDER BY quarter_start)
)
SELECT area_bucket,
       (EXTRACT(YEAR FROM quarter_start)::int::text || '-Q'
            || EXTRACT(QUARTER FROM quarter_start)::int::text) AS period_value,
       deals_count,
       ROUND((med / NULLIF(basis_med, 0))::numeric, 4) AS index_basis,
       ROUND((med / NULLIF(prev_med, 0))::numeric, 4) AS index_previous,
       CASE WHEN prev_q IS NOT NULL THEN
            (EXTRACT(YEAR FROM prev_q)::int::text || '-Q'
                || EXTRACT(QUARTER FROM prev_q)::int::text) END AS prev_period_value
  FROM indexed
 ORDER BY area_bucket, period_value
"""


@pytest.fixture()
def conn():
    with psycopg.connect(_get_dsn(), autocommit=False) as c:
        yield c
        c.rollback()


def _insert_quarter(cur, q_start, bucket_area, price_m2, n) -> None:
    """Insert n single-flat ДДУ rows at given per-unit area + price/m²."""
    rows = [
        ("002001003000", "ДДУ", 66, q_start, bucket_area, 1, price_m2)
        for _ in range(n)
    ]
    cur.executemany(
        "INSERT INTO rd (realestate_type_code, doc_type, region_code, "
        "period_start_date, area, deal_count, price_per_sqm) "
        "VALUES (%s, %s, %s, %s, %s, %s, %s)",
        rows,
    )


def _setup(cur: psycopg.Cursor) -> None:
    cur.execute(
        """
        CREATE TEMP TABLE rd (
            realestate_type_code text, doc_type varchar, region_code smallint,
            period_start_date date, area numeric, deal_count int, price_per_sqm numeric,
            deal_price numeric
        ) ON COMMIT DROP;
        """
    )
    # Bucket 3 (40-60 m²): three quarters, rising median, all >=10 deals.
    _insert_quarter(cur, "2025-04-01", 50, 100000, 12)  # basis
    _insert_quarter(cur, "2025-07-01", 50, 110000, 12)  # +10% vs basis & prev
    _insert_quarter(cur, "2025-10-01", 50, 121000, 12)  # +10% vs prev
    # Bucket 4 (60-80 m²): 2025-Q3 present, 2025-Q4 SPARSE (<10 → filtered),
    # 2026-Q1 present. index_previous for 2026-Q1 must compare to 2025-Q3.
    _insert_quarter(cur, "2025-07-01", 70, 150000, 11)
    _insert_quarter(cur, "2025-10-01", 70, 999999, 3)   # below min_deals → dropped
    _insert_quarter(cur, "2026-01-01", 70, 165000, 11)
    # Packaged-deal trap: one row area=350 deal_count=7 → per-unit 50 m² (bucket 3),
    # NOT bucket 6. Price chosen mid-range so it doesn't move the median much.
    cur.execute(
        "INSERT INTO rd (realestate_type_code, doc_type, region_code, "
        "period_start_date, area, deal_count, price_per_sqm) "
        "VALUES ('002001003000','ДДУ',66,'2025-04-01',350,7,100000)"
    )


def _rows_by_key(rows) -> dict[tuple[int, str], tuple]:
    return {(r[0], r[1]): r for r in rows}


def test_basis_and_previous_index(conn: psycopg.Connection) -> None:
    cur = conn.cursor()
    _setup(cur)
    cur.execute(_INDICATOR_SQL)
    by = _rows_by_key(cur.fetchall())
    # Bucket 3 rising 100k→110k→121k.
    assert by[(3, "2025-Q2")][3] == Decimal("1.0000")  # basis self
    assert by[(3, "2025-Q3")][4] == Decimal("1.1000")  # prev: 110/100
    assert by[(3, "2025-Q4")][4] == Decimal("1.1000")  # prev: 121/110
    assert by[(3, "2025-Q4")][3] == Decimal("1.2100")  # basis: 121/100


def test_packaged_deal_bucketed_by_per_unit_area(conn: psycopg.Connection) -> None:
    """area=350/deal_count=7 → 50 m² → bucket 3, never bucket 6."""
    cur = conn.cursor()
    _setup(cur)
    cur.execute(_INDICATOR_SQL)
    by = _rows_by_key(cur.fetchall())
    # Bucket 6 should have NO rows (the only 100+ candidate was the packaged
    # row, which correctly lands in bucket 3).
    assert not any(k[0] == 6 for k in by), "packaged ДДУ leaked into 100+ bucket"
    # Bucket 3 basis quarter deal count includes the 7-unit packaged row.
    assert by[(3, "2025-Q2")][2] >= 12


def test_prev_period_value_skips_filtered_quarter(conn: psycopg.Connection) -> None:
    """Bucket 4: 2025-Q4 is below min_deals and dropped; 2026-Q1's
    index_previous must compare against 2025-Q3 (honest prev_period_value),
    not the literal previous quarter.
    """
    cur = conn.cursor()
    _setup(cur)
    cur.execute(_INDICATOR_SQL)
    by = _rows_by_key(cur.fetchall())
    assert (4, "2025-Q4") not in by, "sparse quarter should be filtered by min_deals"
    q1 = by[(4, "2026-Q1")]
    assert q1[5] == "2025-Q3", f"prev_period_value must skip dropped Q4, got {q1[5]}"
    # 165000 / 150000 = 1.1000
    assert q1[4] == Decimal("1.1000")
