"""Анализ активных конкурентов ЖК в радиусе от участка.

Issue #112 — Demand: активные конкуренты, продажи ЖК в радиусе 1км за квартал.

Источники:
  domrf_kn_objects            — ЖК с lat/lon, flat_count, obj_class, site_status
  objective_complex_mapping   — domrf_obj_id → objective_complex_name
  objective_corpus_room_month — monthly deals_total_count per project_name
  cad_parcels_geom            — centroid участка (fallback: cad_quarters_geom)
  domrf_kn_flats              — avg price_per_m2 по проданным квартирам

Внимание: velocity coverage ~2.5% — большинство ЖК не имеют маппинга в
objective_complex_mapping. LEFT JOIN гарантирует velocity=0 (не ошибку) для
немаппированных объектов.
"""

from __future__ import annotations

import logging

from sqlalchemy import text
from sqlalchemy.orm import Session

from app.schemas.parcel import (
    Competitor,
    CompetitorsRequest,
    CompetitorsResponse,
    CompetitorsSummary,
)

logger = logging.getLogger(__name__)

# Маппинг time_window → число месяцев (float для деления velocity)
_TIME_WINDOW_MONTHS: dict[str, float] = {
    "last_month": 1.0,
    "last_quarter": 3.0,
    "last_year": 12.0,
}

# site_status значения, считающиеся «активными»
_ACTIVE_STATUSES = frozenset({"sales", "construction"})

# SQL для получения центроида участка
_PARCEL_CENTROID_SQL = text("""
    SELECT ST_X(pt) AS lon, ST_Y(pt) AS lat
    FROM (
        SELECT ST_Centroid(geom) AS pt
          FROM cad_parcels_geom
         WHERE cad_num = :cad_num AND geom IS NOT NULL
        UNION ALL
        SELECT ST_Centroid(geom) AS pt
          FROM cad_quarters_geom
         WHERE cad_number = :quarter AND geom IS NOT NULL
    ) sub
    LIMIT 1
""")

# Основной запрос конкурентов в радиусе.
# Velocity через objective_corpus_room_month (актуальные данные, обновляется еженедельно).
# domrf_kn_sale_graph устарел (данные до 2026-01) — не используется.
# Coverage velocity ~2.5%: большинство obj_id нет в objective_complex_mapping →
# LEFT JOIN → velocity=0 (не ошибка).
_COMPETITORS_SQL = text("""
    WITH latest_obj AS (
        SELECT DISTINCT ON (obj_id)
            obj_id,
            comm_name,
            dev_name,
            obj_class,
            latitude,
            longitude,
            flat_count,
            site_status,
            snapshot_date
        FROM domrf_kn_objects
        WHERE latitude IS NOT NULL
          AND longitude IS NOT NULL
        ORDER BY obj_id, snapshot_date DESC NULLS LAST
    ),
    mapped AS (
        SELECT cm.domrf_obj_id AS obj_id,
               cm.objective_complex_name
          FROM objective_complex_mapping cm
    ),
    velocity AS (
        SELECT
            m.obj_id,
            SUM(COALESCE(crm.deals_total_count, 0))
                / CAST(:time_window_months AS float) AS velocity_per_month
          FROM objective_corpus_room_month crm
          JOIN mapped m ON m.objective_complex_name = crm.project_name
         WHERE crm.report_month >= (NOW() - CAST(:window_interval AS interval))
         GROUP BY m.obj_id
    ),
    distances AS (
        SELECT
            o.obj_id,
            o.comm_name,
            o.dev_name,
            o.obj_class,
            o.latitude,
            o.longitude,
            o.flat_count,
            o.site_status,
            ST_Distance(
                ST_SetSRID(ST_MakePoint(o.longitude, o.latitude), 4326)::geography,
                ST_SetSRID(
                    ST_MakePoint(CAST(:center_lon AS float), CAST(:center_lat AS float)),
                    4326
                )::geography
            ) AS distance_m
        FROM latest_obj o
    )
    SELECT
        d.obj_id,
        d.comm_name,
        d.dev_name,
        d.obj_class,
        d.latitude,
        d.longitude,
        d.flat_count,
        d.site_status,
        d.distance_m,
        COALESCE(v.velocity_per_month, 0.0) AS velocity_per_month
    FROM distances d
    LEFT JOIN velocity v ON v.obj_id = d.obj_id
    WHERE d.distance_m <= CAST(:radius_m AS float)
      AND (
          CAST(:obj_class_filter AS text) IS NULL
          OR d.obj_class = CAST(:obj_class_filter AS text)
      )
    ORDER BY d.distance_m ASC
""")

# Средняя цена м² по квартирам с известной ценой для набора obj_id.
# Фильтр status='sold' убран: поле status в domrf_kn_flats заполнено в ~0.2% строк
# (99.8% NULL) — фильтр давал 0 строк и avg_price_per_m2 всегда None (Issue #112/227).
# AVG по всем квартирам с price_per_m2 IS NOT NULL даёт корректную среднюю цену ЖК.
_AVG_PRICE_SQL = text("""
    SELECT
        f.obj_id,
        AVG(f.price_per_m2) AS avg_price_per_m2
    FROM domrf_kn_flats f
    WHERE f.obj_id = ANY(:obj_ids)
      AND f.price_per_m2 IS NOT NULL
    GROUP BY f.obj_id
""")


def _quarter_from_cad(cad_num: str) -> str:
    """Извлечь кадастровый квартал из номера участка/здания.

    66:41:0303161:123 → 66:41:0303161
    Если формат нестандартный — возвращаем cad_num как есть (fallback).
    """
    parts = cad_num.split(":")
    if len(parts) >= 3:
        return ":".join(parts[:3])
    return cad_num


def get_competitors(
    db: Session,
    cad_num: str,
    request: CompetitorsRequest,
) -> CompetitorsResponse:
    """Получить список конкурентов ЖК в радиусе от участка.

    Шаги:
    1. Найти центроид участка (cad_parcels_geom → cad_quarters_geom fallback).
    2. Выбрать ЖК из domrf_kn_objects в радиусе с velocity из objective_corpus_room_month.
    3. Применить exclude_obj_ids фильтр в Python (избегаем array cast).
    4. Подтянуть avg_price_per_m2 из domrf_kn_flats.
    5. Собрать CompetitorsResponse.

    Raises:
        ValueError: если центроид участка не найден (caller должен вернуть 404).
    """
    quarter = _quarter_from_cad(cad_num)

    # ── 1. Центроид участка ──────────────────────────────────────────────────
    try:
        coord_row = (
            db.execute(
                _PARCEL_CENTROID_SQL,
                {"cad_num": cad_num, "quarter": quarter},
            )
            .mappings()
            .first()
        )
    except Exception:
        logger.exception("competitors: centroid query failed for cad_num=%s", cad_num)
        raise

    if not coord_row:
        raise ValueError(f"Геометрия для {cad_num} не найдена")

    center_lat = float(coord_row["lat"])
    center_lon = float(coord_row["lon"])

    # ── 2. Конкуренты в радиусе ──────────────────────────────────────────────
    time_window_months = _TIME_WINDOW_MONTHS[request.time_window]
    window_interval = f"{int(time_window_months)} months"

    try:
        rows = (
            db.execute(
                _COMPETITORS_SQL,
                {
                    "center_lat": center_lat,
                    "center_lon": center_lon,
                    "radius_m": request.radius_km * 1000.0,
                    "time_window_months": time_window_months,
                    "window_interval": window_interval,
                    "obj_class_filter": request.obj_class_filter,
                },
            )
            .mappings()
            .all()
        )
    except Exception:
        logger.exception(
            "competitors: main query failed for cad_num=%s radius_km=%.2f",
            cad_num,
            request.radius_km,
        )
        raise

    # ── 3. Применить exclude_obj_ids ─────────────────────────────────────────
    exclude_set = set(request.exclude_obj_ids)
    if exclude_set:
        rows = [r for r in rows if int(r["obj_id"]) not in exclude_set]

    if not rows:
        return CompetitorsResponse(
            competitors=[],
            summary=CompetitorsSummary(
                total_competitors=0,
                active_count=0,
                weighted_avg_velocity=0.0,
                radius_km=request.radius_km,
                time_window=request.time_window,
            ),
        )

    obj_ids: list[int] = [int(r["obj_id"]) for r in rows]

    # ── 4. Средняя цена м² (graceful — таблица может быть не заполнена) ──────
    avg_price_map: dict[int, float] = {}
    try:
        price_rows = db.execute(_AVG_PRICE_SQL, {"obj_ids": obj_ids}).mappings().all()
        avg_price_map = {
            int(r["obj_id"]): float(r["avg_price_per_m2"])
            for r in price_rows
            if r["avg_price_per_m2"] is not None
        }
    except Exception:
        logger.warning("competitors: avg_price query failed, continuing without prices")

    # ── 5. Сборка результата ─────────────────────────────────────────────────
    # flats_sold / sold_pct: не доступны из domrf_kn_objects (только flat_count).
    # Можно получить через COUNT(domrf_kn_flats WHERE status='sold') —
    # отложено за MVP, поля остаются None.
    competitors: list[Competitor] = []
    for r in rows:
        obj_id = int(r["obj_id"])
        flats_total = int(r["flat_count"]) if r["flat_count"] is not None else None

        site_status = r["site_status"]
        is_active = site_status in _ACTIVE_STATUSES if site_status else False

        competitors.append(
            Competitor(
                obj_id=obj_id,
                comm_name=r["comm_name"],
                dev_name=r["dev_name"],
                obj_class=r["obj_class"],
                distance_m=round(float(r["distance_m"]), 1),
                lat=float(r["latitude"]),
                lng=float(r["longitude"]),
                stage=site_status,
                flats_total=flats_total,
                flats_sold=None,
                sold_pct=None,
                velocity_per_month=round(float(r["velocity_per_month"]), 2),
                avg_price_per_m2=avg_price_map.get(obj_id),
                is_active=is_active,
            )
        )

    # ── 6. Summary ───────────────────────────────────────────────────────────
    active_count = sum(1 for c in competitors if c.is_active)
    total_velocity = sum(c.velocity_per_month for c in competitors)
    n = len(competitors)
    weighted_avg_velocity = round(total_velocity / n, 2) if n > 0 else 0.0

    summary = CompetitorsSummary(
        total_competitors=n,
        active_count=active_count,
        weighted_avg_velocity=weighted_avg_velocity,
        radius_km=request.radius_km,
        time_window=request.time_window,
    )

    return CompetitorsResponse(competitors=competitors, summary=summary)
