#!/usr/bin/env python3
"""
build_artifact.py — собирает Live HTML-дашборд из insights.json + funnel_raw.json.

Структура:
  - inline JSON в <script id="data">
  - Chart.js с CDN
  - LocalStorage для фильтров

Запуск:
  python3 build_artifact.py \
    --insights insights.json --funnel funnel_raw.json \
    --output artifact.html
"""
from __future__ import annotations

import argparse
import json
import sys
from datetime import date, datetime
from typing import Any


HTML_TEMPLATE = r"""<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<title>Утренний отчёт WB · {{TARGET_DATE}}</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
  :root {
    --bg: #ffffff;
    --bg-soft: #f9fafb;
    --border: #e5e7eb;
    --text: #111827;
    --muted: #6b7280;
    --red: #b91c1c;
    --red-soft: #fef2f2;
    --green: #15803d;
    --green-soft: #f0fdf4;
    --yellow: #b45309;
    --yellow-soft: #fffbeb;
    --indigo: #4338ca;
  }
  * { box-sizing: border-box; }
  body { font-family: -apple-system, "Segoe UI", Inter, system-ui, sans-serif;
         color: var(--text); margin: 0; padding: 16px 24px 64px; background: var(--bg); }
  h1 { font-size: 22px; margin: 0 0 4px; }
  .sub { color: var(--muted); font-size: 13px; margin-bottom: 24px; }
  .header { display:flex; justify-content:space-between; align-items:flex-start; flex-wrap:wrap; gap:12px; }
  .controls { display:flex; gap:8px; align-items:center; }
  button.reload { background: var(--indigo); color:#fff; border:none; padding:8px 14px;
                  border-radius:8px; font-size: 13px; cursor:pointer; }
  .filters { display:flex; gap:8px; flex-wrap:wrap; margin: 8px 0 16px; }
  .filters select { padding: 6px 10px; border: 1px solid var(--border); border-radius:6px; background:#fff; }

  .overview { display:grid; grid-template-columns: repeat(5, 1fr); gap:12px; margin-bottom: 24px; }
  .ov-card { background: var(--bg-soft); border:1px solid var(--border); border-radius:10px; padding:14px; }
  .ov-card .label { font-size:12px; color:var(--muted); }
  .ov-card .value { font-size:22px; font-weight: 700; margin-top: 4px; }
  .ov-card .delta { font-size:13px; margin-top: 4px; }
  .ov-card.down { background: var(--red-soft); }
  .ov-card.up { background: var(--green-soft); }
  .ov-card canvas { margin-top: 8px; max-height: 40px; }

  .section { margin-top: 32px; }
  .section h2 { font-size: 17px; margin: 0 0 10px; display:flex; align-items:center; gap:8px; }
  .section h2 .count { color: var(--muted); font-weight: 400; font-size: 14px; }

  .brand-group { border:1px solid var(--border); border-radius:10px; margin-bottom: 12px; overflow:hidden; }
  .brand-head { background: var(--bg-soft); padding: 10px 14px; cursor: pointer;
                display:flex; justify-content:space-between; font-weight: 600; }
  .brand-body { display:none; padding: 8px; gap:8px; flex-direction: column; }
  .brand-group.open .brand-body { display: flex; }

  .sku { display:grid; grid-template-columns: 1.2fr 1fr 1.3fr 1fr; gap:12px;
         padding: 12px 14px; border:1px solid var(--border); border-radius:8px;
         background:#fff; }
  .sku.drop { background: var(--red-soft); border-color: #fecaca; }
  .sku.growth { background: var(--green-soft); border-color: #bbf7d0; }
  .sku h3 { margin: 0 0 4px; font-size: 14px; }
  .sku .meta { color: var(--muted); font-size: 12px; }
  .sku .num { font-variant-numeric: tabular-nums; }
  .sku .first-break { font-weight: 600; color: var(--red); font-size: 13px; }
  .flags { display:flex; flex-wrap:wrap; gap:6px; margin-top:6px; }
  .flag { font-size:11px; padding:3px 8px; border-radius:12px;
          background:#e5e7eb; color:var(--text); white-space:nowrap; }
  .flag.red { background:#fecaca; color:var(--red); }
  .flag.yellow { background:#fde68a; color:var(--yellow); }
  .flag.green { background:#bbf7d0; color:var(--green); }
  .sparkline { max-height: 80px; }
  .sku a { color: var(--indigo); font-size: 12px; text-decoration: none; }
  .sku a:hover { text-decoration: underline; }

  .negatives { background:#fef2f2; border:1px solid #fecaca; border-radius:10px; padding:14px;
               margin-bottom: 24px; }
  .negatives h2 { margin: 0 0 8px; color: var(--red); }
  .negatives .item { font-size:13px; padding: 6px 0; border-bottom: 1px solid #fee2e2; }
  .negatives .item:last-child { border: 0; }

  details.method { margin-top: 32px; color: var(--muted); }
  details.method summary { cursor: pointer; }
  details.method pre { white-space: pre-wrap; font-size: 12px; background: var(--bg-soft);
                       padding: 12px; border-radius: 8px; }

  .empty { text-align:center; color: var(--muted); padding: 24px; }
  .banner { background:#fef3c7; border:1px solid #fde68a; padding:10px 14px;
            border-radius:8px; margin-bottom: 16px; font-size: 13px; color: #92400e; }
  @media (max-width: 900px) { .overview { grid-template-columns: repeat(2, 1fr); }
                              .sku { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<div class="header">
  <div>
    <h1>Утренний отчёт WB · {{TARGET_DATE}}</h1>
    <div class="sub">Бейзлайн: 7 дней. Анализ обновляется при каждом запуске.</div>
  </div>
  <div class="controls">
    <button class="reload" onclick="window.location.reload()">↻ Обновить</button>
  </div>
</div>

<div class="filters">
  <label>Бренд:
    <select id="brandFilter"><option value="">все</option></select>
  </label>
  <label>Скрыть OOS-кандидатов:
    <input type="checkbox" id="hideOos" />
  </label>
</div>

<div id="banner-host"></div>

<div class="overview" id="overview"></div>

<div id="negatives-host"></div>

<div class="section">
  <h2>🔴 Просадки <span class="count" id="drops-count"></span></h2>
  <div id="drops"></div>
</div>

<div class="section">
  <h2>🟢 Росты <span class="count" id="growths-count"></span></h2>
  <div id="growths"></div>
</div>

<div class="section">
  <h2>⚠️ OOS-кандидаты <span class="count" id="oos-count"></span></h2>
  <div id="oos"></div>
</div>

<details class="method">
  <summary>Методология и фильтры</summary>
  <pre id="method"></pre>
</details>

<script id="data" type="application/json">
{{DATA_JSON}}
</script>

<script>
(function() {
  const data = JSON.parse(document.getElementById('data').textContent);
  const SETTINGS_KEY = 'wb_dd_filters';
  let filters = JSON.parse(localStorage.getItem(SETTINGS_KEY) || '{}');

  // Brand filter dropdown
  const brandSet = new Set();
  for (const s of [...data.drops, ...data.growths, ...data.oos_candidates]) {
    const b = (s.card && s.card.brand) || '—';
    brandSet.add(b);
  }
  const brandSel = document.getElementById('brandFilter');
  [...brandSet].sort().forEach(b => {
    const opt = document.createElement('option');
    opt.value = b; opt.textContent = b;
    if (filters.brand === b) opt.selected = true;
    brandSel.appendChild(opt);
  });
  brandSel.addEventListener('change', () => {
    filters.brand = brandSel.value;
    localStorage.setItem(SETTINGS_KEY, JSON.stringify(filters));
    render();
  });
  const hideOosBox = document.getElementById('hideOos');
  hideOosBox.checked = !!filters.hideOos;
  hideOosBox.addEventListener('change', () => {
    filters.hideOos = hideOosBox.checked;
    localStorage.setItem(SETTINGS_KEY, JSON.stringify(filters));
    render();
  });

  function fmt(num, digits=0) {
    if (num === null || num === undefined || Number.isNaN(num)) return '—';
    return Number(num).toLocaleString('ru-RU', { maximumFractionDigits: digits });
  }
  function fmtPct(v, digits=1) {
    if (v === null || v === undefined) return '—';
    const sign = v >= 0 ? '+' : '';
    return sign + Number(v).toFixed(digits) + '%';
  }
  function escapeHtml(str) {
    if (str === undefined || str === null) return '';
    return String(str).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
  }

  function renderOverview() {
    const ov = data.account_overview || {};
    const d = ov.deltas || {};
    const items = [
      { key: 'openCount', label: 'Показы' },
      { key: 'cartCount', label: 'Корзина' },
      { key: 'orderCount', label: 'Заказы' },
      { key: 'buyoutCount', label: 'Выкупы' },
      { key: 'orderSum', label: 'Выручка ₽' },
    ];
    const host = document.getElementById('overview');
    host.innerHTML = items.map(it => {
      const today = (ov.today || {})[it.key] || 0;
      const pct = (d[it.key] || {}).pct;
      const cls = pct === null || pct === undefined ? '' : (pct <= -20 ? 'down' : (pct >= 20 ? 'up' : ''));
      return `
        <div class="ov-card ${cls}">
          <div class="label">${it.label}</div>
          <div class="value">${fmt(today)}</div>
          <div class="delta">${fmtPct(pct)} к бейзлайну</div>
        </div>`;
    }).join('');

    // Общесистемный баннер
    const bigDrops = items.filter(it => {
      const p = (d[it.key] || {}).pct;
      return p !== null && p !== undefined && p <= -20;
    });
    const bigGrowths = items.filter(it => {
      const p = (d[it.key] || {}).pct;
      return p !== null && p !== undefined && p >= 20;
    });
    let bannerHtml = '';
    if (bigDrops.length) {
      bannerHtml += `<div class="banner">Просел весь кабинет по метрикам: ${bigDrops.map(x=>x.label).join(', ')}. Похоже на общесистемное явление — день недели, акция конкурентов, погода.</div>`;
    }
    if (bigGrowths.length) {
      bannerHtml += `<div class="banner" style="background:#dcfce7;border-color:#bbf7d0;color:#15803d;">Вырос весь кабинет: ${bigGrowths.map(x=>x.label).join(', ')}. Хороший день.</div>`;
    }
    if (data.totals && data.totals.drops === 0 && data.totals.growths === 0 && data.totals.oos_candidates === 0) {
      bannerHtml += `<div class="banner" style="background:#dcfce7;border-color:#bbf7d0;color:#15803d;">Просадок и ростов выше порога не обнаружено. Всё работает в норме.</div>`;
    }
    document.getElementById('banner-host').innerHTML = bannerHtml;
  }

  function renderNegatives() {
    const negs = [];
    for (const s of data.drops.concat(data.oos_candidates)) {
      const r = (s.enrichment && s.enrichment.reviews) || {};
      if (r.flag === 'fresh_negative' && r.sample) {
        negs.push({
          nmID: s.nmID,
          title: (s.card && s.card.title) || '',
          rating: r.sample.rating,
          text: r.sample.text,
        });
      }
    }
    const host = document.getElementById('negatives-host');
    if (!negs.length) { host.innerHTML = ''; return; }
    host.innerHTML = `
      <div class="negatives">
        <h2>🔴 Свежий негатив за последние 48 ч</h2>
        ${negs.slice(0,10).map(n => `
          <div class="item">
            <b>${escapeHtml(n.title)}</b> (nm ${n.nmID}) — ${n.rating}★: «${escapeHtml(n.text)}»
          </div>`).join('')}
      </div>`;
  }

  function flagBadges(s) {
    const out = [];
    const e = s.enrichment || {};
    if (e.stocks) {
      if (e.stocks.flag === 'OOS_full') out.push(['red', '🟥 Полный OOS']);
      else if (e.stocks.flag === 'OOS_warehouse_dropoff') out.push(['red', '🟥 Выпал склад ' + (e.stocks.dropoff_warehouse || '')]);
      else if (e.stocks.flag === 'NO_DATA') out.push(['', '⚪ Остатки н/д']);
    }
    if (e.ads) {
      if (e.ads.flag === 'RK_off') out.push(['red', '🟥 РК отключена']);
      else if (e.ads.flag === 'RK_dropped') out.push(['yellow', '🟡 РК просела']);
      else if (e.ads.flag === 'RK_boosted') out.push(['yellow', '🟡 РК разогналась']);
      else if (e.ads.flag === 'NO_DATA') out.push(['', '⚪ Реклама н/д']);
    }
    if (e.price) {
      if (e.price.flag === 'price_changed') out.push(['yellow', '🟡 Цена изменилась ' + fmtPct(e.price.delta_pct)]);
      else if (e.price.flag === 'no_history') out.push(['', '⚪ Цена — нет истории']);
    }
    if (e.reviews) {
      if (e.reviews.flag === 'fresh_negative') out.push(['red', `🟥 Свежий негатив (${e.reviews.fresh_negative_count})`]);
    }
    return out.map(([cls, t]) => `<span class="flag ${cls}">${escapeHtml(t)}</span>`).join('');
  }

  function brandGroups(skus, sectionKey) {
    const groups = {};
    for (const s of skus) {
      const b = (s.card && s.card.brand) || '—';
      if (filters.brand && filters.brand !== b) continue;
      groups[b] = groups[b] || [];
      groups[b].push(s);
    }
    const sortedBrands = Object.keys(groups).sort((a, b) => {
      const sumA = groups[a].reduce((acc, s) => acc + Math.abs(((s.deltas && s.deltas.orderSum) || {}).abs || 0), 0);
      const sumB = groups[b].reduce((acc, s) => acc + Math.abs(((s.deltas && s.deltas.orderSum) || {}).abs || 0), 0);
      return sumB - sumA;
    });
    return sortedBrands.map(brand => ({ brand, items: groups[brand] }));
  }

  function skuCard(s, sectionClass) {
    const nm = s.nmID;
    const title = (s.card && s.card.title) || '';
    const today = s.today || {};
    const base = s.baseline || {};
    const deltas = s.deltas || {};
    const revPct = ((deltas.orderSum || {}).pct);
    const firstBreak = s.first_break;
    const breakLabels = {
      openCount: 'ПОКАЗЫ',
      addToCartConversion: 'CR показ→корзина',
      cartCount: 'КОРЗИНА',
      cartToOrderConversion: 'CR корзина→заказ',
      orderCount: 'ЗАКАЗЫ',
      buyoutCount: 'ВЫКУП',
      orderSum: 'ВЫРУЧКА',
    };
    return `
      <div class="sku ${sectionClass}">
        <div>
          <h3>${escapeHtml(title)}</h3>
          <div class="meta">nm ${nm} · ${escapeHtml((s.card && s.card.subjectName) || '')}</div>
          <div class="flags">${flagBadges(s)}</div>
          <a href="https://www.wildberries.ru/catalog/${nm}/detail.aspx" target="_blank" rel="noopener">открыть на WB →</a>
        </div>
        <div class="num">
          <div><b>Выручка ₽</b></div>
          <div>${fmt(today.orderSum || 0)} <span style="color:var(--muted);">/ ${fmt(base.orderSum || 0)}</span></div>
          <div class="${revPct !== null && revPct <= -20 ? 'first-break' : ''}">${fmtPct(revPct)}</div>
        </div>
        <div class="num">
          <div><b>Воронка</b> (показы → корзина → заказы → выкупы)</div>
          <div>${fmt(today.openCount)} → ${fmt(today.cartCount)} → ${fmt(today.orderCount)} → ${fmt(today.buyoutCount)}</div>
          ${firstBreak ? `<div class="first-break">Сломалось на: ${breakLabels[firstBreak] || firstBreak}</div>` : ''}
        </div>
        <div>
          <canvas class="sparkline" id="sp-${sectionClass}-${nm}"></canvas>
        </div>
      </div>`;
  }

  function renderSection(hostId, skus, sectionClass) {
    const host = document.getElementById(hostId);
    if (!skus.length) {
      host.innerHTML = '<div class="empty">Здесь пусто. Хорошо!</div>';
      return;
    }
    const groups = brandGroups(skus, sectionClass);
    host.innerHTML = groups.map((g, idx) => `
      <div class="brand-group ${idx === 0 ? 'open' : ''}">
        <div class="brand-head" onclick="this.parentElement.classList.toggle('open')">
          <span>${escapeHtml(g.brand)} (${g.items.length})</span>
          <span style="color:var(--muted);">▾</span>
        </div>
        <div class="brand-body">
          ${g.items.map(s => skuCard(s, sectionClass)).join('')}
        </div>
      </div>`).join('');
  }

  function drawSparks() {
    for (const s of [...data.drops, ...data.growths, ...data.oos_candidates]) {
      const sectionClass = s.category === 'growth' ? 'growth' : (s.category === 'oos_candidate' ? 'oos' : 'drop');
      const el = document.getElementById(`sp-${sectionClass}-${s.nmID}`);
      if (!el) continue;
      const series = s.history_14d || [];
      new Chart(el.getContext('2d'), {
        type: 'line',
        data: {
          labels: series.map(p => p.date.slice(5)),
          datasets: [{
            label: 'Заказы', data: series.map(p => p.orderCount), borderColor: '#4338ca',
            backgroundColor: 'rgba(67,56,202,0.1)', tension: 0.3, fill: true, pointRadius: 0,
          }],
        },
        options: {
          responsive: true, maintainAspectRatio: false,
          plugins: { legend: { display: false } },
          scales: { x: { display: false }, y: { display: false } },
        },
      });
    }
  }

  function renderMethod() {
    const lines = [
      `Анализируемая дата: ${data.target_date}`,
      `Бейзлайн: ${(data.baseline_dates || []).join(', ')}`,
      `Акционные дни (исключены): ${((data.settings_used || {}).promo_dates || []).join(', ') || '—'}`,
      `Порог просадки: ${(data.settings_used || {}).drop_pct || '—'}%`,
      `Порог роста: ${(data.settings_used || {}).growth_pct || '—'}%`,
      `Мин. заказов/день: ${(data.settings_used || {}).min_baseline_orders_per_day}`,
      `Мин. выручки/день: ${(data.settings_used || {}).min_baseline_revenue_per_day_rub} ₽`,
      `Правило «0 заказов»: ${(data.settings_used || {}).zero_orders_rule}`,
      `Просадок: ${data.totals.drops}, ростов: ${data.totals.growths}, OOS-кандидатов: ${data.totals.oos_candidates}, ok: ${data.totals.ok}`,
      `Пропущено новых SKU (<7 дней): ${data.totals.skipped_new}`,
      `Пропущено за низкий объём: ${data.totals.skipped_low_volume}`,
    ];
    document.getElementById('method').textContent = lines.join('\n');
  }

  function render() {
    renderOverview();
    renderNegatives();
    document.getElementById('drops-count').textContent = `· ${data.drops.length}`;
    document.getElementById('growths-count').textContent = `· ${data.growths.length}`;
    document.getElementById('oos-count').textContent = `· ${data.oos_candidates.length}`;
    renderSection('drops', data.drops, 'drop');
    renderSection('growths', data.growths, 'growth');
    if (filters.hideOos) {
      document.getElementById('oos').innerHTML = '<div class="empty">Скрыто пользователем</div>';
    } else {
      renderSection('oos', data.oos_candidates, 'oos');
    }
    renderMethod();
    setTimeout(drawSparks, 100);
  }

  render();
})();
</script>
</body>
</html>
"""


def attach_history_14d(insights: dict[str, Any], funnel: dict[str, Any]) -> None:
    """Прикрепляем к каждому SKU историю за N дней для sparkline.
    Передаём ТОЛЬКО {date, orderCount} — sparkline рисует одну линию, остальные поля
    раздували бы payload в 10x без всякой пользы для UI."""
    by_nm = funnel.get("by_nm", {})
    for section in ("drops", "growths", "oos_candidates"):
        for s in insights.get(section, []):
            nm = str(s["nmID"])
            dates = (by_nm.get(nm) or {}).get("dates", {}) or {}
            sorted_items = sorted(dates.items())[-14:]
            s["history_14d"] = [
                {"date": d, "orderCount": vals.get("orderCount", 0)}
                for d, vals in sorted_items
            ]


def slim_insights_for_html(insights: dict[str, Any]) -> dict[str, Any]:
    """Отрезаем поля, которые HTML не использует: ok_skus (только в Excel), skipped (только в Excel),
    raw history полей кроме orderCount, лишние ключи enrichment."""
    SECTION_KEEP = ("drops", "growths", "oos_candidates")

    def slim_sku(s: dict[str, Any]) -> dict[str, Any]:
        # Урезаем today/baseline до набора метрик, которые реально использует UI
        keep_metrics = (
            "openCount", "cartCount", "orderCount", "orderSum", "buyoutCount",
            "addToCartConversion", "cartToOrderConversion", "buyoutPercent",
        )
        out = {
            "nmID": s.get("nmID"),
            "card": s.get("card"),
            "category": s.get("category"),
            "first_break": s.get("first_break"),
            "today": {k: (s.get("today") or {}).get(k) for k in keep_metrics},
            "baseline": {k: (s.get("baseline") or {}).get(k) for k in keep_metrics},
            "deltas": s.get("deltas"),
            "enrichment": s.get("enrichment"),
            "history_14d": s.get("history_14d"),
        }
        return out

    slim = {
        "target_date": insights.get("target_date"),
        "baseline_dates": insights.get("baseline_dates"),
        "settings_used": insights.get("settings_used"),
        "account_overview": insights.get("account_overview"),
        "totals": insights.get("totals"),
    }
    for sec in SECTION_KEEP:
        slim[sec] = [slim_sku(s) for s in insights.get(sec, [])]
    return slim


def main() -> int:
    p = argparse.ArgumentParser()
    p.add_argument("--insights", required=True)
    p.add_argument("--funnel", required=True)
    p.add_argument("--output", required=True)
    args = p.parse_args()

    with open(args.insights, encoding="utf-8") as f:
        insights = json.load(f)
    with open(args.funnel, encoding="utf-8") as f:
        funnel = json.load(f)

    attach_history_14d(insights, funnel)
    slim = slim_insights_for_html(insights)

    # КОМПАКТНЫЙ JSON для inline — separators без пробелов экономит ~30% размера
    data_json = json.dumps(slim, ensure_ascii=False, separators=(",", ":"))
    html = HTML_TEMPLATE \
        .replace("{{TARGET_DATE}}", insights["target_date"]) \
        .replace("{{DATA_JSON}}", data_json)
    with open(args.output, "w", encoding="utf-8") as f:
        f.write(html)
    print(f"[artifact] saved {args.output} (inline JSON: {len(data_json):,} bytes)", file=sys.stderr)
    return 0


if __name__ == "__main__":
    sys.exit(main())
