"""PDF render для ТЗ (Layout Analysis #113 PR D).

Pattern reference: backend/app/services/exporters/pdf.py (existing WeasyPrint).
"""

from __future__ import annotations

import datetime as dt
import html as _html
import logging

from app.schemas.parcel import BestLayoutsResponse

logger = logging.getLogger(__name__)


def build_layout_tz_html(
    response: BestLayoutsResponse,
    *,
    cad_num: str,
    parcel_address: str | None = None,
    radius_km: float,
    time_window: str,
) -> str:
    """Собрать HTML ТЗ из best-layouts response (PURE — без WeasyPrint/БД/сети).

    Вынесено из :func:`render_layout_tz_pdf`, чтобы HTML-разметку (наличие блоков,
    RU-микрокопию, форматирование ₽/м²) можно было проверять юнит-тестами БЕЗ
    native GTK/Pango (WeasyPrint на macOS/Windows отсутствует). PDF-обёртка ниже
    просто рендерит этот HTML.

    Args:
        response: BestLayoutsResponse от /best-layouts endpoint
        cad_num: кадастровый номер участка
        parcel_address: optional human address (если known через geocoder)
        radius_km: радиус анализа конкурентов
        time_window: окно анализа (last_month/quarter/year)

    Returns:
        HTML-строка документа (готова для WeasyPrint).
    """
    today = dt.date.today().strftime("%d.%m.%Y")
    safe_cad = _html.escape(cad_num)
    safe_addr = _html.escape(parcel_address) if parcel_address else None
    safe_time_window = _html.escape(time_window)
    addr_line = f"<p>Адрес: {safe_addr}</p>" if safe_addr else ""

    def _price_cell(val: float | None) -> str:
        if val is None:
            return "<td>—</td>"
        return f"<td>{val:,.0f}".replace(",", " ") + " ₽</td>"

    def _price_m2_cell(val: float | None) -> str:
        """Ячейка цены ₽/м² (тыс-разделитель — пробел). None → «—» (graceful)."""
        if val is None:
            return "<td>—</td>"
        return f"<td>{val:,.0f}".replace(",", " ") + " ₽/м²</td>"

    def _area_cell(val: float | None) -> str:
        """Ячейка площади, м² (1 знак). None → «—» (graceful)."""
        if val is None:
            return "<td>—</td>"
        return f"<td>{val:.1f} м²</td>"

    # Top layouts table rows
    top_rows = "".join(
        "<tr>"
        f"<td>{r.rank}</td>"
        f"<td>{_html.escape(r.room_bucket)}</td>"
        f"<td>{_html.escape(r.area_bin)}</td>"
        f"<td>{r.velocity_per_month:.1f}</td>"
        f"<td>{r.avg_area_m2:.1f}</td>"
        f"{_price_cell(r.avg_price_per_m2_rub)}"
        f"<td>{r.total_sold_in_window}</td>"
        "</tr>"
        for r in response.top_layouts
    )

    # Recommendation mix table rows
    mix_rows = "".join(
        "<tr>"
        f"<td>{_html.escape(m.room_bucket)}</td>"
        f"<td>{m.pct}%</td>"
        f"<td>{m.abs_units if m.abs_units is not None else '—'}</td>"
        f"<td>{f'{m.avg_target_area_m2:.1f}' if m.avg_target_area_m2 is not None else '—'}</td>"
        "</tr>"
        for m in response.recommendation_for_tz.mix
    )

    # §4.2 «По предложению (без темпа продаж)» (#2177 шаг 3). Аддитивный fallback-блок:
    # структура текущего предложения (комплекс/тип/площадь/лотов/медиана ₽/м²) по
    # комплексам, сматченным по ядру имени, но БЕЗ velocity. Graceful — на старых
    # снапшотах поле отсутствует / пустой список (default []), тогда секцию не рисуем.
    supply_only = getattr(response, "supply_only_layouts", None) or []
    supply_only_rows = "".join(
        "<tr>"
        f"<td>{_html.escape(s.project_name)}</td>"
        f"<td>{_html.escape(s.room_bucket)}</td>"
        f"<td>{_html.escape(s.area_bin)}</td>"
        f"{_area_cell(s.median_area_m2)}"
        f"<td>{s.lots_count}</td>"
        f"{_price_m2_cell(s.median_price_m2)}"
        "</tr>"
        for s in supply_only
    )
    supply_only_section = (
        f"""
  <h2>По предложению (без темпа продаж)</h2>
  <p class="caveat">
    Комплексы сматчены по ядру имени на объявления Объектива, но по ним НЕТ сделок
    (темпа продаж) — ниже только структура остатков в продаже (objective_lots),
    БЕЗ ранжирования по скорости. Дополняет топ продаж выше, не заменяет его.
  </p>
  <table>
    <thead><tr>
      <th>Комплекс</th><th>Комнаты</th><th>Площадь</th>
      <th>Ср. площадь</th><th>Лотов в продаже</th><th>Медиана, ₽/м²</th>
    </tr></thead>
    <tbody>{supply_only_rows}</tbody>
  </table>
"""
        if supply_only
        else ""
    )

    rec = response.recommendation_for_tz
    safe_rationale = _html.escape(rec.rationale_text)
    weighted_price = (
        f"{rec.weighted_avg_price_per_m2_rub:,.0f}".replace(",", " ") + " ₽/м²"
        if rec.weighted_avg_price_per_m2_rub is not None
        else "нет данных"
    )

    dq = response.data_quality

    html = f"""<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>ТЗ на проектирование — {safe_cad}</title>
<style>
  body {{ font-family: 'Helvetica', sans-serif; font-size: 11pt; color: #222; }}
  h1 {{ font-size: 18pt; margin-bottom: 0.2em; }}
  h2 {{ font-size: 14pt; margin-top: 1.2em; border-bottom: 1px solid #ccc; }}
  .meta {{ color: #666; font-size: 10pt; margin-bottom: 1em; }}
  table {{ width: 100%; border-collapse: collapse; margin: 0.5em 0; }}
  th, td {{ padding: 6px 10px; border: 1px solid #ddd; text-align: left; }}
  th {{ background: #f5f5f5; font-weight: bold; }}
  .rationale {{ background: #f8f8f8; padding: 10px; border-left: 3px solid #4a90e2;
               margin: 1em 0; }}
  .caveat {{ background: #fffbeb; padding: 8px 10px; border-left: 3px solid #c9a132;
             margin: 0.6em 0; color: #7a5c12; font-size: 9.5pt; }}
  .footer {{ margin-top: 2em; padding-top: 1em; border-top: 1px solid #ddd;
             color: #888; font-size: 9pt; }}
  .confidence-high {{ color: #2a8c2a; }}
  .confidence-medium {{ color: #c9a132; }}
  .confidence-low {{ color: #b03434; }}
</style>
</head>
<body>
  <h1>Техническое задание на проектирование (data-driven)</h1>
  <div class="meta">
    <p>Кадастровый номер: <strong>{safe_cad}</strong></p>
    {addr_line}
    <p>Радиус анализа: {radius_km} км · Окно: {safe_time_window}</p>
    <p>Дата формирования: {today}</p>
  </div>

  <h2>Рекомендуемая структура квартирографии (unit-mix)</h2>
  <div class="rationale">{safe_rationale}</div>
  <table>
    <thead><tr>
      <th>Комнатность</th><th>Доля</th><th>Кол-во (от target)</th><th>Целевая площадь, м²</th>
    </tr></thead>
    <tbody>{mix_rows}</tbody>
  </table>
  <p>Средневзвешенная цена benchmark: <strong>{weighted_price}</strong></p>
  <p>Основано на {rec.based_on_obj_count} ЖК / {rec.based_on_total_deals} сделок</p>
  <p>Период данных:
    {rec.data_window_start.strftime("%d.%m.%Y")} – {rec.data_window_end.strftime("%d.%m.%Y")}
  </p>

  <h2>Топ планировок конкурентов по продажам</h2>
  <table>
    <thead><tr>
      <th>#</th><th>Комнаты</th><th>Площадь</th><th>Продажи/мес</th>
      <th>Ср. площадь, м²</th><th>Ср. цена, ₽/м²</th><th>Продано (окно)</th>
    </tr></thead>
    <tbody>{top_rows}</tbody>
  </table>
{supply_only_section}
  <h2>Качество данных</h2>
  <p>
    Покрытие: {dq.objects_with_velocity_data} из
    {dq.objects_total_in_radius} комплексов с данными velocity
    ({dq.velocity_coverage_pct:.1f}%)
  </p>
  <p>
    Уверенность:
    <span class="confidence-{dq.confidence}">
      {dq.confidence.upper()}
    </span>
  </p>

  <div class="footer">
    <p>GenDesign Site Finder · сгенерировано из данных DOM.РФ + Objective + Росреестр</p>
    <p>Phase 2.1: без layout_type (евро/классика/панорама) и balcony_count.</p>
  </div>
</body>
</html>"""

    return html


def render_layout_tz_pdf(
    response: BestLayoutsResponse,
    *,
    cad_num: str,
    parcel_address: str | None = None,
    radius_km: float,
    time_window: str,
) -> bytes:
    """Render ТЗ PDF от best-layouts response.

    Тонкая обёртка над :func:`build_layout_tz_html` — собирает HTML (PURE) и
    рендерит его WeasyPrint'ом в PDF-байты.

    Args:
        response: BestLayoutsResponse от /best-layouts endpoint
        cad_num: кадастровый номер участка
        parcel_address: optional human address (если known через geocoder)
        radius_km: радиус анализа конкурентов
        time_window: окно анализа (last_month/quarter/year)

    Returns:
        PDF bytes (готово для StreamingResponse)
    """
    html = build_layout_tz_html(
        response,
        cad_num=cad_num,
        parcel_address=parcel_address,
        radius_km=radius_km,
        time_window=time_window,
    )

    # WeasyPrint импортируем локально — тяжёлый; не нужен при импорте модуля
    # (иначе ломает сбор pytest на хостах без native-libs, напр. macOS).
    from weasyprint import HTML

    pdf_bytes = HTML(string=html).write_pdf()
    logger.info("Generated layout TZ PDF for cad %s: %d bytes", cad_num, len(pdf_bytes))
    return pdf_bytes
