"""Генерация одностраничного PDF-снимка кадастрового участка.

Использует WeasyPrint + Jinja2. Шрифты — DejaVu Sans из системы (Dockerfile)
или из пакета weasyprint (font fallback). Шаблон: app/templates/parcel_snapshot.html.
"""

import datetime
import logging
import pathlib
from typing import Any

from jinja2 import Environment, FileSystemLoader, select_autoescape

logger = logging.getLogger(__name__)

# Путь к директории шаблонов: app/templates/parcel_snapshot.html (#1739).
# Файл лежит в app/services/exporters/, поэтому до app/ — 3 уровня вверх
# (exporters → services → app), затем templates. parent.parent давал
# несуществующий app/services/templates → TemplateNotFound → 500 на snapshot.
_TEMPLATE_DIR = pathlib.Path(__file__).parent.parent.parent / "templates"

# Системные пути DejaVu Sans (Ubuntu/Debian Docker-образ + Alpine резерв)
_DEJAVU_CANDIDATES: list[str] = [
    "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
    "/usr/share/fonts/dejavu/DejaVuSans.ttf",
    "/usr/share/fonts/TTF/DejaVuSans.ttf",
]

_CATEGORY_RU: dict[str, str] = {
    "school": "Школа",
    "kindergarten": "Детский сад",
    "pharmacy": "Аптека",
    "hospital": "Больница",
    "shop_mall": "ТЦ",
    "shop_supermarket": "Супермаркет",
    "shop_small": "Магазин",
    "park": "Парк",
    "bus_stop": "Автобус",
    "metro_stop": "Метро",
    "tram_stop": "Трамвай",
}

# Веса POI-категорий — должны совпадать с _POI_WEIGHTS в parcels.py.
# Дублированы здесь чтобы exporter не импортировал из api-слоя.
_POI_WEIGHTS: dict[str, float] = {
    "school": 1.5,
    "kindergarten": 1.5,
    "pharmacy": 0.8,
    "hospital": 0.6,
    "shop_mall": 1.2,
    "shop_supermarket": 1.0,
    "shop_small": 0.5,
    "park": 1.8,
    "bus_stop": 0.3,
    "metro_stop": 1.5,
    "tram_stop": -0.5,
}

_WALK_SPEED_M_PER_MIN: float = 80.0  # ~5 км/ч


def _find_font_url() -> str:
    """Вернуть file:// URL для DejaVu Sans или пустую строку (system fallback).

    WeasyPrint умеет сам находить системные шрифты через fonttools/fontconfig,
    поэтому пустая строка допустима — шрифт тогда подбирается CSS generic.

    #1739: при пустой строке шаблон НЕ эмитит `@font-face { src: url('') }`
    (обёрнут в `{% if font_url %}`). Иначе WeasyPrint пытался резолвить пустой
    URL → 'failed to load font' ошибка вместо тихого fallback на Arial/sans-serif.
    """
    for path in _DEJAVU_CANDIDATES:
        if pathlib.Path(path).exists():
            return f"file://{path}"
    logger.warning(
        "snapshot_pdf: DejaVu Sans не найден в стандартных путях — используем system fallback"
    )
    return ""


def _format_cost(value: float | None) -> str:
    """Форматировать кадастровую стоимость в читаемый вид (млн/тыс ₽)."""
    if value is None:
        return "—"
    if value >= 1_000_000:
        return f"{value / 1_000_000:.1f} млн ₽"
    if value >= 1_000:
        return f"{value / 1_000:.0f} тыс ₽"
    return f"{value:.0f} ₽"


def _build_poi_items(poi_rows: list[dict[str, Any]], limit: int = 7) -> list[dict[str, Any]]:
    """Вычислить weighted_score для каждого POI и вернуть топ-N отсортированных.

    Формула: weighted_score = weight * max(0, 1 - distance_m / 1000)
    Отрицательные вклады (трамвай) — не включаем в топ-список.
    """
    items: list[dict[str, Any]] = []
    for p in poi_rows:
        cat: str = p.get("category", "")
        w = _POI_WEIGHTS.get(cat, 0.0)
        distance_m = float(p.get("distance_m") or 0)
        decay = max(0.0, 1.0 - distance_m / 1000.0)
        score = round(w * decay, 2)
        if score <= 0:
            continue
        walk_min = max(1, round(distance_m / _WALK_SPEED_M_PER_MIN))
        items.append(
            {
                "category_ru": _CATEGORY_RU.get(cat, cat),
                "name": p.get("name") or "",
                "distance_m": round(distance_m),
                "walk_min": walk_min,
                "weighted_score": score,
            }
        )
    items.sort(key=lambda x: x["weighted_score"], reverse=True)
    return items[:limit]


def generate_snapshot_pdf(
    *,
    cad_num: str,
    address: str | None,
    district: str | None,
    area_m2: float | None,
    cadastral_cost_rub: float | None,
    land_category: str | None,
    vri: str | None,
    last_update: str | None,
    poi_rows: list[dict[str, Any]],
    competitor_rows: list[dict[str, Any]],
    competitors_limit: int = 5,
) -> bytes:
    """Сгенерировать PDF-снимок участка (1 страница A4).

    Аргументы:
        cad_num: кадастровый номер.
        address: адрес из cad_parcels.
        district: район города.
        area_m2: площадь в кв. м (конвертируем в га для отображения).
        cadastral_cost_rub: кадастровая стоимость в рублях.
        land_category: категория земель.
        vri: вид разрешённого использования.
        last_update: строка даты последнего обновления данных.
        poi_rows: сырые строки из osm_poi_ekb (category, name, distance_m).
        competitor_rows: строки конкурентов из domrf_kn_objects.
        competitors_limit: сколько конкурентов выводить (3-5 по ТЗ).

    Возвращает: bytes PDF-документа.
    """
    # WeasyPrint импортируем локально — тяжёлый; не нужен при импорте модуля
    try:
        from weasyprint import HTML
    except ImportError as exc:
        raise RuntimeError(
            "WeasyPrint не установлен. Добавь 'weasyprint>=62.0' в pyproject.toml."
        ) from exc

    env = Environment(
        loader=FileSystemLoader(str(_TEMPLATE_DIR)),
        autoescape=select_autoescape(["html"]),
    )
    template = env.get_template("parcel_snapshot.html")

    area_ha = f"{area_m2 / 10_000:.2f}" if area_m2 else "—"
    poi_items = _build_poi_items(poi_rows, limit=7)

    # Конкуренты — берём топ N БЛИЖАЙШИХ. competitor_rows приходят отсортированными
    # по flat_count DESC (крупнейшие ЖК), поэтому сначала пересортировываем весь
    # список по distance_m ASC, и лишь затем срезаем N — иначе в блок попадали бы
    # 5 крупнейших из радиуса, а не непосредственное конкурентное окружение пятна.
    competitors_display = sorted(
        competitor_rows,
        key=lambda r: float(r.get("distance_m") or 0),
    )[:competitors_limit]
    competitors_ctx: list[dict[str, Any]] = [
        {
            "comm_name": r.get("comm_name"),
            "dev_name": r.get("dev_name"),
            "obj_class": r.get("obj_class"),
            "flat_count": r.get("flat_count"),
            "distance_m": round(float(r.get("distance_m") or 0)),
        }
        for r in competitors_display
    ]

    generated_at = datetime.datetime.now(tz=datetime.UTC).strftime("%d.%m.%Y %H:%M UTC")

    html_str = template.render(
        cad_num=cad_num,
        address=address,
        district=district,
        area_ha=area_ha,
        cadastral_cost=_format_cost(cadastral_cost_rub),
        land_category=land_category,
        vri=vri,
        last_update=last_update or "—",
        poi_items=poi_items,
        competitors=competitors_ctx,
        generated_at=generated_at,
        font_url=_find_font_url(),
    )

    logger.info(
        "snapshot_pdf: rendering PDF for %s (%d POI, %d competitors)",
        cad_num,
        len(poi_items),
        len(competitors_ctx),
    )

    pdf_bytes: bytes = HTML(string=html_str, base_url=str(_TEMPLATE_DIR)).write_pdf()
    return pdf_bytes
