#!/usr/bin/env python3
"""Génère le rapport commercial hebdomadaire GSC, Umami, WooCommerce et Lighthouse."""

import argparse
import base64
import html
import json
import os
import subprocess
import tempfile
import time as time_module
import urllib.parse
import urllib.request
from datetime import date, datetime, time, timedelta, timezone
from pathlib import Path

SITE_URL = "https://arnaud-merigeau.fr"
UMAMI_SITE_ID = "a44e818c-2c21-471d-aa97-00bc34c2f23f"
LOGO_URL = "https://www.arnaud-merigeau.fr/wp-content/uploads/2017/04/arnaud-merigeau-logo-1.png"
EVENT_LABELS = {
    "view_cart": "Vue panier",
    "begin_checkout": "Début de commande",
    "view_blog_post": "Vue article",
    "view_product": "Vue produit",
    "add_to_cart": "Ajout au panier",
    "purchase": "Achat",
    "generate_lead": "Formulaire envoyé",
    "schedule_click": "Clic prise de rendez-vous",
    "schedule_event": "Rendez-vous réservé",
    "contact_tab": "Onglet contact",
    "cta_contact": "Clic contact",
    "affiliate_program_click": "Clic programme d’affiliation",
    "affiliate_click": "Clic affilié",
    "outbound_click": "Clic sortant",
}


def request_json(url, data=None, headers=None):
    body = json.dumps(data).encode() if data is not None else None
    request = urllib.request.Request(
        url,
        data=body,
        headers={"Content-Type": "application/json", **(headers or {})},
        method="POST" if data is not None else "GET",
    )
    with urllib.request.urlopen(request, timeout=120) as response:
        return json.loads(response.read())


def b64url(value):
    return base64.urlsafe_b64encode(value).rstrip(b"=").decode()


def google_access_token():
    account = json.loads(os.environ["GSC_SERVICE_ACCOUNT_JSON"])
    now = int(datetime.now(timezone.utc).timestamp())
    header = b64url(json.dumps({"alg": "RS256", "typ": "JWT"}, separators=(",", ":")).encode())
    payload = b64url(
        json.dumps(
            {
                "iss": account["client_email"],
                "scope": "https://www.googleapis.com/auth/webmasters.readonly",
                "aud": account.get("token_uri", "https://oauth2.googleapis.com/token"),
                "iat": now,
                "exp": now + 3600,
            },
            separators=(",", ":"),
        ).encode()
    )
    unsigned = f"{header}.{payload}".encode()

    with tempfile.NamedTemporaryFile("w", delete=False) as key_file:
        key_file.write(account["private_key"])
        key_path = key_file.name
    try:
        signature = subprocess.run(
            ["openssl", "dgst", "-sha256", "-sign", key_path],
            input=unsigned,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            check=True,
        ).stdout
    finally:
        os.unlink(key_path)

    assertion = f"{header}.{payload}.{b64url(signature)}"
    encoded = urllib.parse.urlencode(
        {
            "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
            "assertion": assertion,
        }
    ).encode()
    request = urllib.request.Request(
        account.get("token_uri", "https://oauth2.googleapis.com/token"),
        data=encoded,
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )
    with urllib.request.urlopen(request, timeout=30) as response:
        return json.loads(response.read())["access_token"]


def gsc_period(token, start, end):
    endpoint = (
        "https://searchconsole.googleapis.com/webmasters/v3/sites/"
        + urllib.parse.quote(os.environ.get("GSC_SITE_URL", "https://www.arnaud-merigeau.fr/"), safe="")
        + "/searchAnalytics/query"
    )

    def query(dimensions=None, limit=25000):
        body = {
            "startDate": start.isoformat(),
            "endDate": end.isoformat(),
            "rowLimit": limit,
            "dataState": "final",
        }
        if dimensions:
            body["dimensions"] = dimensions
        return request_json(endpoint, body, {"Authorization": f"Bearer {token}"}).get("rows", [])

    total_rows = query(limit=1)
    total = total_rows[0] if total_rows else {}
    return {
        "start": start.isoformat(),
        "end": end.isoformat(),
        "summary": {
            "clicks": total.get("clicks", 0),
            "impressions": total.get("impressions", 0),
            "ctr": total.get("ctr", 0),
            "position": total.get("position", 0),
        },
        "queries": query(["query"], 100),
        "pages": query(["page"], 100),
    }


def collect_gsc(start, end, previous_start, previous_end):
    token = google_access_token()
    return {
        "current": gsc_period(token, start, end),
        "previous": gsc_period(token, previous_start, previous_end),
    }


def umami_api(token, path, data=None):
    host = os.environ.get("UMAMI_HOST", "https://analytics.arnaud-merigeau.fr").rstrip("/")
    return request_json(f"{host}{path}", data, {"Authorization": f"Bearer {token}"})


def umami_period(token, start, end):
    start_at = int(time_module.mktime(datetime.combine(start, time.min).timetuple()) * 1000)
    end_at = int(time_module.mktime(datetime.combine(end + timedelta(days=1), time.min).timetuple()) * 1000) - 1
    prefix = f"/api/websites/{UMAMI_SITE_ID}"
    return {
        "start": start.isoformat(),
        "end": end.isoformat(),
        "stats": umami_api(token, f"{prefix}/stats?startAt={start_at}&endAt={end_at}"),
        "events": umami_api(token, f"{prefix}/metrics?type=event&startAt={start_at}&endAt={end_at}"),
        "urls": umami_api(token, f"{prefix}/metrics?type=url&startAt={start_at}&endAt={end_at}")[:100],
        "referrers": umami_api(token, f"{prefix}/metrics?type=referrer&startAt={start_at}&endAt={end_at}")[:100],
    }


def collect_umami(start, end, previous_start, previous_end):
    login = request_json(
        os.environ.get("UMAMI_HOST", "https://analytics.arnaud-merigeau.fr").rstrip("/") + "/api/auth/login",
        {
            "username": os.environ["UMAMI_ADMIN_USER"],
            "password": os.environ["UMAMI_ADMIN_PASSWORD"],
        },
    )
    token = login["token"]
    return {
        "current": umami_period(token, start, end),
        "previous": umami_period(token, previous_start, previous_end),
    }


def collect_pagespeed():
    key = os.environ.get("PAGESPEED_API_KEY", "")
    urls = os.environ.get(
        "PAGESPEED_URLS",
        ",".join(
            [
                SITE_URL + "/",
                SITE_URL + "/freelance-prestashop/",
                SITE_URL + "/produit/module-export-comptable-pour-prestashop/",
            ]
        ),
    ).split(",")
    results = []

    for url in filter(None, map(str.strip, urls)):
        query = urllib.parse.urlencode(
            {
                "url": url,
                "strategy": "mobile",
                "category": "performance",
                **({"key": key} if key else {}),
            }
        )
        response = request_json(f"https://www.googleapis.com/pagespeedonline/v5/runPagespeed?{query}")
        lighthouse = response["lighthouseResult"]
        audits = lighthouse["audits"]
        results.append(
            {
                "url": url,
                "score": round(lighthouse["categories"]["performance"]["score"] * 100),
                "lcp_ms": audits["largest-contentful-paint"].get("numericValue"),
                "cls": audits["cumulative-layout-shift"].get("numericValue"),
                "tbt_ms": audits["total-blocking-time"].get("numericValue"),
                "fcp_ms": audits["first-contentful-paint"].get("numericValue"),
            }
        )
    return results


def metric_value(stats, name):
    value = stats.get(name, 0)
    if isinstance(value, dict):
        return value.get("value", 0)
    return value or 0


def metric_rows(rows):
    normalized = []
    for row in rows or []:
        name = row.get("x", row.get("name", row.get("value", "")))
        value = row.get("y", row.get("count", row.get("views", 0)))
        normalized.append({"name": name, "value": value})
    return normalized


def event_count(period, event):
    for row in metric_rows(period.get("events", [])):
        if row["name"] == event:
            return row["value"]
    return 0


def load_json(path, default):
    if not path or not Path(path).exists():
        return default
    return json.loads(Path(path).read_text())


def collect(args):
    today = date.today()
    end = today - timedelta(days=1)
    start = end - timedelta(days=6)
    previous_end = start - timedelta(days=1)
    previous_start = previous_end - timedelta(days=6)
    errors = []
    report = {
        "generated_at": datetime.now().astimezone().isoformat(),
        "period": {"start": start.isoformat(), "end": end.isoformat()},
        "previous_period": {"start": previous_start.isoformat(), "end": previous_end.isoformat()},
        "gsc": {},
        "umami": {},
        "woocommerce": load_json(args.woocommerce, {}),
        "pagespeed": [],
        "errors": errors,
    }

    for name, callback in (
        ("Search Console", lambda: collect_gsc(start, end, previous_start, previous_end)),
        ("Umami", lambda: collect_umami(start, end, previous_start, previous_end)),
        ("Lighthouse", collect_pagespeed),
    ):
        try:
            report[{"Search Console": "gsc", "Umami": "umami", "Lighthouse": "pagespeed"}[name]] = callback()
        except Exception as error:
            errors.append(f"{name}: {error}")

    if not report["woocommerce"]:
        errors.append("WooCommerce: export absent")
    return report


def esc(value):
    return html.escape(str(value))


def number(value, decimals=0):
    try:
        text = f"{float(value):,.{decimals}f}"
    except (TypeError, ValueError):
        return "n/d"
    return text.replace(",", " ").replace(".", ",")


def percent(value):
    return number(float(value) * 100, 1) + " %"


def delta(current, previous, percent_value=False):
    try:
        current = float(current)
        previous = float(previous)
    except (TypeError, ValueError):
        return "n/d"
    if percent_value:
        return f"{(current - previous) * 100:+.1f} pt".replace(".", ",")
    if previous == 0:
        return "nouveau" if current else "0 %"
    return f"{((current - previous) / previous) * 100:+.1f} %".replace(".", ",")


def period_days(report):
    start = datetime.strptime(report["period"]["start"], "%Y-%m-%d").date()
    end = datetime.strptime(report["period"]["end"], "%Y-%m-%d").date()
    return (end - start).days + 1


def change_state(current, previous):
    try:
        difference = float(current) - float(previous)
    except (TypeError, ValueError):
        return "neutral"
    return "positive" if difference > 0 else "negative" if difference < 0 else "neutral"


def delta_badge(current, previous, percent_value=False, email=False):
    state = change_state(current, previous)
    label = esc(delta(current, previous, percent_value))
    if not email:
        return f'<span class="delta {state}">{label}</span>'
    colors = {
        "positive": ("#dcfce7", "#166534"),
        "negative": ("#fee2e2", "#b91c1c"),
        "neutral": ("#f2f4f7", "#475467"),
    }
    background, color = colors[state]
    return (
        f'<span style="display:inline-block;padding:4px 9px;border-radius:999px;'
        f'background:{background};color:{color};font-weight:700">{label}</span>'
    )


def status_badge(display, value, good, average, higher_is_better=False):
    if value is None:
        return ('<span class="metric neutral">n/d</span>', "")
    if higher_is_better:
        state = "good" if value >= good else "average" if value >= average else "bad"
    else:
        state = "good" if value <= good else "average" if value <= average else "bad"
    return (f'<span class="metric {state}">{esc(display)}</span>', value)


def table(headers, rows, classes="", sortable=False, default_sort=None, sort_types=None):
    sort_types = sort_types or ["text"] * len(headers)
    heading = ""
    for index, header in enumerate(headers):
        if sortable:
            heading += (
                f'<th data-sort-type="{esc(sort_types[index])}" aria-sort="none">'
                f'<button type="button" data-column="{index}">{esc(header)} '
                '<span class="sort-icon" aria-hidden="true">↕</span></button></th>'
            )
        else:
            heading += f"<th>{esc(header)}</th>"

    def render_cell(cell):
        if isinstance(cell, tuple):
            display, sort_value = cell
            return f'<td data-sort-value="{esc(sort_value)}">{display}</td>'
        return f"<td>{cell}</td>"

    body = "".join(
        "<tr>" + "".join(render_cell(cell) for cell in row) + "</tr>"
        for row in rows
    )
    attributes = f'class="{classes}"'
    if sortable:
        attributes += ' data-sortable="true"'
    if default_sort:
        attributes += f' data-default-sort="{default_sort[0]}" data-default-direction="{default_sort[1]}"'
    return f'<div class="table-wrap"><table {attributes}><thead><tr>{heading}</tr></thead><tbody>{body}</tbody></table></div>'


def gsc_rows(rows):
    return [
        [
            (esc((row.get("keys") or [""])[0]), (row.get("keys") or [""])[0].casefold()),
            (number(row.get("clicks", 0)), row.get("clicks", 0)),
            (number(row.get("impressions", 0)), row.get("impressions", 0)),
            (percent(row.get("ctr", 0)), row.get("ctr", 0)),
            (number(row.get("position", 0), 1), row.get("position", 0)),
        ]
        for row in rows[:30]
    ]


def render(report, report_url, history):
    gsc_current = report.get("gsc", {}).get("current", {})
    gsc_previous = report.get("gsc", {}).get("previous", {})
    gc = gsc_current.get("summary", {})
    gp = gsc_previous.get("summary", {})
    umami_current = report.get("umami", {}).get("current", {})
    umami_previous = report.get("umami", {}).get("previous", {})
    uc = umami_current.get("stats", {})
    up = umami_previous.get("stats", {})
    wc = report.get("woocommerce", {}).get("current", {})
    wp = report.get("woocommerce", {}).get("previous", {})

    kpis = [
        ["Clics Google", number(gc.get("clicks", 0)), delta_badge(gc.get("clicks", 0), gp.get("clicks", 0))],
        ["Impressions Google", number(gc.get("impressions", 0)), delta_badge(gc.get("impressions", 0), gp.get("impressions", 0))],
        ["CTR Google", percent(gc.get("ctr", 0)), delta_badge(gc.get("ctr", 0), gp.get("ctr", 0), True)],
        ["Visiteurs Umami", number(metric_value(uc, "visitors")), delta_badge(metric_value(uc, "visitors"), metric_value(up, "visitors"))],
        ["Pages vues Umami", number(metric_value(uc, "pageviews")), delta_badge(metric_value(uc, "pageviews"), metric_value(up, "pageviews"))],
        ["Formulaires envoyés", number(event_count(umami_current, "generate_lead")), delta_badge(event_count(umami_current, "generate_lead"), event_count(umami_previous, "generate_lead"))],
        ["Rendez-vous réservés", number(event_count(umami_current, "schedule_event")), delta_badge(event_count(umami_current, "schedule_event"), event_count(umami_previous, "schedule_event"))],
        ["Commandes payées", number(wc.get("orders", 0)), delta_badge(wc.get("orders", 0), wp.get("orders", 0))],
        ["CA commandé", number(wc.get("revenue", 0), 2) + " €", delta_badge(wc.get("revenue", 0), wp.get("revenue", 0))],
    ]

    event_rows = [
        [
            (esc(row["name"]), row["name"].casefold()),
            (esc(EVENT_LABELS.get(row["name"], row["name"].replace("_", " ").capitalize())), EVENT_LABELS.get(row["name"], row["name"]).casefold()),
            (number(row["value"]), row["value"]),
        ]
        for row in metric_rows(umami_current.get("events", []))[:30]
    ]
    url_rows = [
        [(esc(row["name"]), row["name"].casefold()), (number(row["value"]), row["value"])]
        for row in metric_rows(umami_current.get("urls", []))[:30]
    ]
    referrer_rows = [
        [
            (esc(row["name"] or "Accès direct"), (row["name"] or "Accès direct").casefold()),
            (number(row["value"]), row["value"]),
        ]
        for row in metric_rows(umami_current.get("referrers", []))[:30]
    ]
    product_rows = [
        [(esc(name), name.casefold()), (number(quantity), quantity)]
        for name, quantity in wc.get("top_products", {}).items()
    ]
    pagespeed_rows = [
        [
            (
                f'<a href="{esc(row["url"])}">{esc(urllib.parse.urlparse(row["url"]).path or "/")}</a>',
                (urllib.parse.urlparse(row["url"]).path or "/").casefold(),
            ),
            status_badge(number(row.get("score")), row.get("score"), 90, 50, True),
            status_badge(number(row.get("fcp_ms"), 0) + " ms", row.get("fcp_ms"), 1800, 3000),
            status_badge(number(row.get("lcp_ms"), 0) + " ms", row.get("lcp_ms"), 2500, 4000),
            status_badge(number(row.get("cls"), 3), row.get("cls"), 0.1, 0.25),
            status_badge(number(row.get("tbt_ms"), 0) + " ms", row.get("tbt_ms"), 200, 600),
        ]
        for row in report.get("pagespeed", [])
    ]
    history_rows = [
        [
            esc(item.get("period", "")),
            number(item.get("gsc_clicks", 0)),
            number(item.get("visitors", 0)),
            number(item.get("leads", 0)),
            number(item.get("orders", 0)),
            number(item.get("revenue", 0), 2) + " €",
        ]
        for item in reversed(history[-12:])
    ]
    errors = "".join(f"<li>{esc(error)}</li>" for error in report.get("errors", []))
    warning = f'<aside class="warning"><strong>Données manquantes</strong><ul>{errors}</ul></aside>' if errors else ""
    period = f'{report["period"]["start"]} au {report["period"]["end"]}'
    duration = period_days(report)

    styles = """
    :root{color-scheme:light;--ink:#17212b;--muted:#667085;--line:#e4e7ec;--paper:#fff;--wash:#f7f8fa;--accent:#e64b35;--green:#166534;--green-bg:#dcfce7;--orange:#9a3412;--orange-bg:#ffedd5;--red:#b91c1c;--red-bg:#fee2e2}
    *{box-sizing:border-box}body{margin:0;background:var(--wash);color:var(--ink);font:15px/1.5 system-ui,-apple-system,sans-serif}
    main{max-width:1180px;margin:auto;padding:40px 20px 80px}header{display:flex;justify-content:space-between;gap:24px;align-items:end;margin-bottom:28px}
    h1{font-size:clamp(30px,5vw,52px);line-height:1;margin:0 0 10px}h2{font-size:24px;margin:34px 0 12px}p{margin:0;color:var(--muted)}
    .stamp{text-align:right}.table-wrap{overflow:auto;background:var(--paper);border:1px solid var(--line);border-radius:12px}
    table{border-collapse:collapse;width:100%;min-width:620px}th,td{padding:11px 14px;border-bottom:1px solid var(--line);text-align:left;white-space:nowrap}
    th{font-size:12px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);background:#fbfcfd}th button{all:unset;cursor:pointer;display:flex;align-items:center;gap:5px;width:100%}th button:focus-visible{outline:2px solid var(--accent);outline-offset:3px}.sort-icon{font-size:13px;color:#98a2b3}tbody tr:last-child td{border:0}
    .kpis td:nth-child(2){font-weight:750;font-size:18px}.delta,.metric{display:inline-block;padding:4px 9px;border-radius:999px;font-weight:750}.delta.positive,.metric.good{background:var(--green-bg);color:var(--green)}.delta.negative,.metric.bad{background:var(--red-bg);color:var(--red)}.delta.neutral,.metric.neutral{background:#f2f4f7;color:#475467}.metric.average{background:var(--orange-bg);color:var(--orange)}
    .warning{margin:20px 0;padding:16px 20px;border-left:4px solid var(--accent);background:#fff4f1}
    a{color:#b52e1c}footer{margin-top:40px;color:var(--muted)}@media(max-width:700px){header{display:block}.stamp{text-align:left;margin-top:12px}main{padding-top:24px}}
    """
    sorting_script = """
    <script>
    document.querySelectorAll('table[data-sortable]').forEach(function(table) {
      var body = table.tBodies[0];
      var headers = Array.prototype.slice.call(table.querySelectorAll('th'));
      var collator = new Intl.Collator('fr', {numeric: true, sensitivity: 'base'});
      function sort(column, direction) {
        var type = headers[column].dataset.sortType;
        var rows = Array.prototype.slice.call(body.rows).map(function(row, index) { return {row: row, index: index}; });
        rows.sort(function(a, b) {
          var left = a.row.cells[column].dataset.sortValue || a.row.cells[column].textContent.trim();
          var right = b.row.cells[column].dataset.sortValue || b.row.cells[column].textContent.trim();
          if (left === '' && right !== '') return 1;
          if (right === '' && left !== '') return -1;
          var result = type === 'number' ? Number(left) - Number(right) : collator.compare(left, right);
          if (result === 0) result = a.index - b.index;
          return direction === 'asc' ? result : -result;
        });
        rows.forEach(function(item) { body.appendChild(item.row); });
        headers.forEach(function(header, index) {
          header.setAttribute('aria-sort', index === column ? (direction === 'asc' ? 'ascending' : 'descending') : 'none');
          header.querySelector('.sort-icon').textContent = index === column ? (direction === 'asc' ? '▲' : '▼') : '↕';
        });
        table.dataset.sortedColumn = column;
        table.dataset.sortedDirection = direction;
      }
      headers.forEach(function(header, column) {
        header.querySelector('button').addEventListener('click', function() {
          var sameColumn = table.dataset.sortedColumn === String(column);
          sort(column, sameColumn && table.dataset.sortedDirection === 'asc' ? 'desc' : 'asc');
        });
      });
      if (table.dataset.defaultSort !== undefined) {
        sort(Number(table.dataset.defaultSort), table.dataset.defaultDirection || 'asc');
      }
    });
    </script>
    """

    return f"""<!doctype html>
<html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex,nofollow,noarchive"><title>Rapport SEO commercial hebdomadaire</title><style>{styles}</style></head>
<body><main><header><div><h1>Rapport SEO commercial</h1><p>Search Console, Umami, WooCommerce et Lighthouse</p></div>
<p class="stamp">Période : {esc(period)}<br>Durée : {duration} jours<br>Mis à jour le {esc(report["generated_at"][:16].replace("T", " "))}</p></header>
{warning}
<h2>Indicateurs principaux</h2>{table(["Indicateur", "Valeur", f"Écart sur {duration} jours"], kpis, "kpis")}
<h2>Performance mobile</h2>{table(["Page", "Score", "FCP", "LCP", "CLS", "TBT"], pagespeed_rows, sortable=True, default_sort=(1, "desc"), sort_types=["text", "number", "number", "number", "number", "number"])}
<h2>Historique sur 12 semaines</h2>{table(["Semaine", "Clics Google", "Visiteurs", "Leads", "Commandes", "CA"], history_rows)}
<h2>Requêtes Google</h2>{table(["Requête", "Clics", "Impressions", "CTR", "Position"], gsc_rows(gsc_current.get("queries", [])), sortable=True, default_sort=(2, "desc"), sort_types=["text", "number", "number", "number", "number"])}
<h2>Pages dans Google</h2>{table(["Page", "Clics", "Impressions", "CTR", "Position"], gsc_rows(gsc_current.get("pages", [])), sortable=True, default_sort=(2, "desc"), sort_types=["text", "number", "number", "number", "number"])}
<h2>Événements Umami</h2>{table(["Événement", "Traduction", "Occurrences"], event_rows, sortable=True, default_sort=(2, "desc"), sort_types=["text", "text", "number"])}
<h2>Pages vues dans Umami</h2>{table(["URL", "Vues"], url_rows, sortable=True, default_sort=(1, "desc"), sort_types=["text", "number"])}
<h2>Sources de trafic</h2>{table(["Source", "Visites"], referrer_rows, sortable=True, default_sort=(1, "desc"), sort_types=["text", "number"])}
<h2>Produits vendus</h2>{table(["Produit", "Quantité"], product_rows, sortable=True, default_sort=(1, "desc"), sort_types=["text", "number"])}
<footer>Page accessible uniquement avec son adresse complète. Ne partagez pas ce lien publiquement.</footer>
</main>{sorting_script}</body></html>"""


def compact_snapshot(report):
    gc = report.get("gsc", {}).get("current", {}).get("summary", {})
    uc = report.get("umami", {}).get("current", {})
    wc = report.get("woocommerce", {}).get("current", {})
    return {
        "period": f'{report["period"]["start"]} - {report["period"]["end"]}',
        "gsc_clicks": gc.get("clicks", 0),
        "visitors": metric_value(uc.get("stats", {}), "visitors"),
        "leads": event_count(uc, "generate_lead") + event_count(uc, "schedule_event"),
        "orders": wc.get("orders", 0),
        "revenue": wc.get("revenue", 0),
    }


def render_email(report, report_url):
    snapshot = compact_snapshot(report)
    gc_previous = report.get("gsc", {}).get("previous", {}).get("summary", {})
    up = report.get("umami", {}).get("previous", {})
    wp = report.get("woocommerce", {}).get("previous", {})
    previous_leads = event_count(up, "generate_lead") + event_count(up, "schedule_event")
    duration = period_days(report)
    lines = [
        ("Clics Google", number(snapshot["gsc_clicks"]), snapshot["gsc_clicks"], gc_previous.get("clicks", 0)),
        ("Visiteurs", number(snapshot["visitors"]), snapshot["visitors"], metric_value(up.get("stats", {}), "visitors")),
        ("Leads", number(snapshot["leads"]), snapshot["leads"], previous_leads),
        ("Commandes", number(snapshot["orders"]), snapshot["orders"], wp.get("orders", 0)),
        ("CA commandé", number(snapshot["revenue"], 2) + " €", snapshot["revenue"], wp.get("revenue", 0)),
    ]
    rows = "".join(
        f"<tr><td style='padding:13px 14px;border-bottom:1px solid #eaecf0;color:#344054'>{esc(label)}</td>"
        f"<td style='padding:13px 14px;border-bottom:1px solid #eaecf0;font-size:17px'><strong>{esc(value)}</strong></td>"
        f"<td style='padding:13px 14px;border-bottom:1px solid #eaecf0'>{delta_badge(current, previous, email=True)}</td></tr>"
        for label, value, current, previous in lines
    )
    missing = ""
    if report.get("errors"):
        missing = (
            '<div style="margin:20px 0;padding:14px 16px;background:#fff4f1;'
            'border-left:4px solid #e64b35;color:#7a271a">Certaines sources n’ont pas répondu : '
            + esc("; ".join(report["errors"]))
            + "</div>"
        )
    return f"""<div style="margin:0;padding:30px 12px;background:#f3f5f7;font-family:Arial,sans-serif;color:#17212b;line-height:1.5">
<div style="max-width:680px;margin:0 auto;background:#ffffff;border:1px solid #e4e7ec;border-radius:16px;overflow:hidden">
<div style="padding:26px 30px 20px;border-bottom:1px solid #e4e7ec">
<img src="{LOGO_URL}" width="80" alt="Arnaud Mérigeau" style="display:block;max-width:80px;height:auto;margin:0 0 20px">
<h1 style="margin:0 0 10px;font-size:27px;line-height:1.2;color:#17212b">Rapport SEO commercial</h1>
<p style="margin:0;color:#667085">Période du {esc(report["period"]["start"])} au {esc(report["period"]["end"])}<br>
Durée : {duration} jours</p>
</div>
<div style="padding:24px 30px 30px">
<table role="presentation" style="border-collapse:separate;border-spacing:0;width:100%;border:1px solid #e4e7ec;border-radius:10px;overflow:hidden">
<tr style="background:#f8fafc"><th style="padding:11px 14px;text-align:left;color:#667085;font-size:12px;text-transform:uppercase">Indicateur</th>
<th style="padding:11px 14px;text-align:left;color:#667085;font-size:12px;text-transform:uppercase">Valeur</th>
<th style="padding:11px 14px;text-align:left;color:#667085;font-size:12px;text-transform:uppercase">Écart sur {duration} jours</th></tr>{rows}</table>
{missing}
<p style="margin:24px 0 0"><a href="{esc(report_url)}" style="display:inline-block;padding:12px 18px;background:#17212b;color:#ffffff;text-decoration:none;border-radius:7px;font-weight:700">Ouvrir le tableau complet</a></p>
<p style="margin:22px 0 0;color:#98a2b3;font-size:12px">Search Console, Umami, WooCommerce et PageSpeed mobile.</p>
</div></div></div>"""


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--woocommerce")
    parser.add_argument("--history")
    parser.add_argument("--output", required=True)
    parser.add_argument("--report-url", required=True)
    args = parser.parse_args()

    output = Path(args.output)
    output.mkdir(parents=True, exist_ok=True)
    report = collect(args)
    history = load_json(args.history, [])
    snapshot = compact_snapshot(report)
    history = [item for item in history if item.get("period") != snapshot["period"]]
    history.append(snapshot)
    history = history[-52:]

    (output / "index.html").write_text(render(report, args.report_url, history))
    (output / "report.json").write_text(json.dumps(report, ensure_ascii=False, indent=2))
    (output / "history.json").write_text(json.dumps(history, ensure_ascii=False, indent=2))
    (output / "email.html").write_text(render_email(report, args.report_url))
    (output / "subject.txt").write_text(
        f'Rapport SEO du {report["period"]["start"]} au {report["period"]["end"]}'
    )
    (output / ".htaccess").write_text(
        'Options -Indexes\n<IfModule mod_headers.c>\nHeader set X-Robots-Tag "noindex, nofollow, noarchive"\n</IfModule>\n'
    )


if __name__ == "__main__":
    main()
