#!/usr/bin/env python3
"""
Gerador de Relatorio Semanal — PX3 Lab
Pipeline: PADRAO PX3
Janela: sexta 9:01h ate proxima sexta 9:00h (America/Sao_Paulo)

Uso:
    python3 gerar_relatorio.py                 # semana atual (corrente)
    python3 gerar_relatorio.py --semana 2026-25  # semana especifica (ISO)
    python3 gerar_relatorio.py --enviar         # envia via Telegram outbox apos gerar
"""

import argparse
import html
import json
import os
import re
import sys
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timedelta, timezone
from pathlib import Path
from zoneinfo import ZoneInfo

import requests

# ============================================================
# Config
# ============================================================
SCRIPT_DIR = Path(__file__).resolve().parent
PX3LAB_DIR = SCRIPT_DIR.parent
sys.path.insert(0, str(PX3LAB_DIR))

from config_ghl import PX3_TOKEN, PX3_LOCATION_ID, PX3_PIPELINE_PADRAO  # noqa: E402

GHL_BASE = "https://services.leadconnectorhq.com"
HEADERS = {
    "Authorization": f"Bearer {PX3_TOKEN}",
    "Version": "2021-07-28",
    "Accept": "application/json",
    "Content-Type": "application/json",
}

TZ_BR = ZoneInfo("America/Sao_Paulo")
TZ_UTC = ZoneInfo("UTC")

# Stages mapeados via API (Pipeline PADRAO PX3)
STAGES = {
    "3232fe51-0b8a-4738-b0f5-ace6c1c97ebc": "NOVO",
    "d4c6c1c5-6688-4a0a-9145-155ca7354950": "CLIENTE RESPONDEU",
    "788ed387-087e-4669-8aad-98ae6d25adf1": "QUALIFICADO",
    "dcd3892d-43e5-47f0-8b40-94fa155afc81": "LEAD EM TESTE",
    "d67905bd-b19c-4cd4-9244-51a9809c699c": "PROPOSTA",
    "bf2ac7d3-fa18-470f-a27d-538598314391": "GANHAMOS",
    "6367d1b0-308a-46be-a930-cf599f5318bd": "PERDIDO",
    "cbaf555c-3b17-4524-97c4-0782df16e28d": "JA E CLIENTE",
}

STAGE_ORDER = [
    "NOVO",
    "CLIENTE RESPONDEU",
    "QUALIFICADO",
    "LEAD EM TESTE",
    "PROPOSTA",
    "GANHAMOS",
    "JA E CLIENTE",
    "PERDIDO",
]

STAGE_COLORS = {
    "NOVO": "#3b82f6",
    "CLIENTE RESPONDEU": "#06b6d4",
    "QUALIFICADO": "#8b5cf6",
    "LEAD EM TESTE": "#f59e0b",
    "PROPOSTA": "#ec4899",
    "GANHAMOS": "#10b981",
    "JA E CLIENTE": "#22d3ee",
    "PERDIDO": "#ef4444",
}

# ============================================================
# Janela temporal
# ============================================================
def calcular_janela(semana_iso: str | None = None) -> tuple[datetime, datetime, str]:
    """
    Retorna (inicio, fim, label) em UTC.
    Janela = sexta 09:01 (BR) ate proxima sexta 09:00 (BR).

    - Se semana_iso=None: janela da semana corrente (ultima sexta 09:01 ate agora).
    - Se semana_iso=YYYY-WW: encontra a sexta que pertence aquela semana ISO.
    """
    if semana_iso:
        m = re.match(r"^(\d{4})-W?(\d{1,2})$", semana_iso)
        if not m:
            raise ValueError("Formato esperado: YYYY-WW (ex: 2026-25)")
        ano, semana = int(m.group(1)), int(m.group(2))
        # Sexta da semana ISO
        # ISO: segunda=1 ... sexta=5
        base = datetime.fromisocalendar(ano, semana, 5).replace(
            hour=9, minute=1, second=0, microsecond=0, tzinfo=TZ_BR
        )
        inicio_br = base
        fim_br = (base + timedelta(days=7)).replace(hour=9, minute=0, second=0)
    else:
        agora_br = datetime.now(TZ_BR)
        # Encontrar a ultima sexta-feira as 09:01
        # weekday(): seg=0, ter=1, qua=2, qui=3, sex=4, sab=5, dom=6
        dias_desde_sexta = (agora_br.weekday() - 4) % 7
        ultima_sexta = (agora_br - timedelta(days=dias_desde_sexta)).replace(
            hour=9, minute=1, second=0, microsecond=0
        )
        # Se hoje e sexta antes das 09:01, usar a sexta anterior
        if agora_br.weekday() == 4 and agora_br < ultima_sexta:
            ultima_sexta -= timedelta(days=7)
        inicio_br = ultima_sexta
        fim_br = inicio_br + timedelta(days=7) - timedelta(minutes=1)
        # Se ainda nao chegou no fim da janela, usa agora como fim
        if agora_br < fim_br:
            fim_br = agora_br

    inicio_utc = inicio_br.astimezone(TZ_UTC)
    fim_utc = fim_br.astimezone(TZ_UTC)
    label = f"{inicio_br.strftime('%d/%m/%Y %H:%M')} a {fim_br.strftime('%d/%m/%Y %H:%M')} (Brasilia)"
    return inicio_utc, fim_utc, label, inicio_br, fim_br


# ============================================================
# GHL API
# ============================================================
def buscar_oportunidades(status_filter: str | None = None) -> list[dict]:
    """Busca todas as oportunidades paginando. status_filter: open|won|lost|abandoned|all"""
    todas = []
    start_after = None
    start_after_id = None
    paginas = 0
    while True:
        paginas += 1
        params = {
            "location_id": PX3_LOCATION_ID,
            "pipeline_id": PX3_PIPELINE_PADRAO,
            "limit": 100,
        }
        if status_filter and status_filter != "all":
            params["status"] = status_filter
        if start_after and start_after_id:
            params["startAfter"] = start_after
            params["startAfterId"] = start_after_id

        for tentativa in range(3):
            try:
                r = requests.get(
                    f"{GHL_BASE}/opportunities/search",
                    headers=HEADERS,
                    params=params,
                    timeout=30,
                )
                if r.status_code == 429:
                    time.sleep(2 * (tentativa + 1))
                    continue
                r.raise_for_status()
                break
            except Exception as e:
                if tentativa == 2:
                    raise
                time.sleep(1)

        data = r.json()
        opps = data.get("opportunities", [])
        todas.extend(opps)
        meta = data.get("meta", {})
        start_after = meta.get("startAfter")
        start_after_id = meta.get("startAfterId")
        if not meta.get("nextPageUrl") or not opps:
            break
        if paginas > 50:
            break
        time.sleep(0.15)

    return todas


def parse_iso_utc(s: str) -> datetime | None:
    if not s:
        return None
    try:
        if s.endswith("Z"):
            s = s[:-1] + "+00:00"
        return datetime.fromisoformat(s).astimezone(TZ_UTC)
    except Exception:
        return None


def buscar_notas(contact_id: str) -> list[dict]:
    """Busca notas de um contato. Retorna lista (pode ser vazia)."""
    for tentativa in range(3):
        try:
            r = requests.get(
                f"{GHL_BASE}/contacts/{contact_id}/notes",
                headers=HEADERS,
                timeout=15,
            )
            if r.status_code == 429:
                time.sleep(2 * (tentativa + 1))
                continue
            if r.status_code == 404:
                return []
            r.raise_for_status()
            return r.json().get("notes", []) or []
        except Exception:
            if tentativa == 2:
                return []
            time.sleep(0.5)
    return []


def buscar_contato(contact_id: str) -> dict:
    """Busca dados detalhados do contato (phone, name)."""
    for tentativa in range(3):
        try:
            r = requests.get(
                f"{GHL_BASE}/contacts/{contact_id}",
                headers=HEADERS,
                timeout=15,
            )
            if r.status_code == 429:
                time.sleep(2 * (tentativa + 1))
                continue
            if r.status_code == 404:
                return {}
            r.raise_for_status()
            return r.json().get("contact", {}) or {}
        except Exception:
            if tentativa == 2:
                return {}
            time.sleep(0.5)
    return {}


# ============================================================
# Limpeza HTML / texto
# ============================================================
RE_TAG = re.compile(r"<[^>]+>")
RE_WS = re.compile(r"\s+")


def strip_html(s: str) -> str:
    if not s:
        return ""
    txt = RE_TAG.sub(" ", s)
    txt = html.unescape(txt)
    return RE_WS.sub(" ", txt).strip()


def truncar(s: str, n: int = 150) -> str:
    s = s.strip()
    if len(s) <= n:
        return s
    return s[: n - 1].rstrip() + "…"


# ============================================================
# Coleta principal
# ============================================================
def coletar_dados(inicio_utc: datetime, fim_utc: datetime) -> dict:
    print(f"[*] Coletando oportunidades (todas) ...", flush=True)
    todas = buscar_oportunidades(status_filter="all")
    print(f"[*] Total bruto pipeline: {len(todas)}", flush=True)

    # Filtrar pela janela: createdAt OU lastStageChangeAt dentro da janela
    # Para captar movimentacao na semana mesmo se lead e antigo.
    dentro = []
    novos_na_semana = []  # createdAt dentro da janela
    for op in todas:
        created = parse_iso_utc(op.get("createdAt", ""))
        last_change = parse_iso_utc(op.get("lastStageChangeAt", "")) or parse_iso_utc(
            op.get("lastStatusChangeAt", "")
        )
        if created and inicio_utc <= created <= fim_utc:
            novos_na_semana.append(op)
            dentro.append(op)
        elif last_change and inicio_utc <= last_change <= fim_utc:
            dentro.append(op)

    print(
        f"[*] Oportunidades com atividade na janela: {len(dentro)} "
        f"(novos: {len(novos_na_semana)})",
        flush=True,
    )

    # Buscar notas em paralelo
    contact_ids = list({op["contactId"] for op in dentro if op.get("contactId")})
    print(f"[*] Buscando notas de {len(contact_ids)} contatos ...", flush=True)
    notas_por_contato = {}

    def _fetch(cid):
        time.sleep(0.05)
        return cid, buscar_notas(cid)

    with ThreadPoolExecutor(max_workers=5) as ex:
        futs = [ex.submit(_fetch, cid) for cid in contact_ids]
        for fut in as_completed(futs):
            cid, notas = fut.result()
            notas_por_contato[cid] = notas

    # Buscar contatos completos pra ter phone (em paralelo)
    print(f"[*] Buscando dados de contato ...", flush=True)
    contatos_full = {}

    def _fetch_c(cid):
        time.sleep(0.05)
        return cid, buscar_contato(cid)

    with ThreadPoolExecutor(max_workers=5) as ex:
        futs = [ex.submit(_fetch_c, cid) for cid in contact_ids]
        for fut in as_completed(futs):
            cid, ct = fut.result()
            contatos_full[cid] = ct

    return {
        "todas": todas,
        "dentro": dentro,
        "novos_na_semana": novos_na_semana,
        "notas_por_contato": notas_por_contato,
        "contatos_full": contatos_full,
    }


# ============================================================
# Agregacao
# ============================================================
def agregar(dados: dict, inicio_utc: datetime, fim_utc: datetime) -> dict:
    dentro = dados["dentro"]
    novos = dados["novos_na_semana"]
    notas_por_contato = dados["notas_por_contato"]
    contatos_full = dados["contatos_full"]

    # Contagem por stage (com base no estado ATUAL da oportunidade que teve
    # atividade na semana)
    contagem_stage = {nome: 0 for nome in STAGE_ORDER}
    for op in dentro:
        nome = STAGES.get(op.get("pipelineStageId"), "DESCONHECIDO")
        # status "lost" forca categoria PERDIDO mesmo se stage diferente
        if op.get("status") == "lost":
            nome = "PERDIDO"
        elif op.get("status") == "won":
            nome = "GANHAMOS"
        contagem_stage[nome] = contagem_stage.get(nome, 0) + 1

    total = len(dentro)
    novos_total = len(novos)
    qualificados = (
        contagem_stage.get("QUALIFICADO", 0)
        + contagem_stage.get("LEAD EM TESTE", 0)
        + contagem_stage.get("PROPOSTA", 0)
        + contagem_stage.get("GANHAMOS", 0)
    )
    fechamentos = contagem_stage.get("GANHAMOS", 0)
    perdidos = contagem_stage.get("PERDIDO", 0)
    taxa_qual = (qualificados / novos_total * 100) if novos_total else 0
    taxa_fech = (fechamentos / novos_total * 100) if novos_total else 0

    # Linhas individuais
    linhas = []
    for op in dentro:
        cid = op.get("contactId")
        contato = contatos_full.get(cid, {}) or {}
        nome = (
            (contato.get("contactName") or "").strip()
            or f"{contato.get('firstName','') or ''} {contato.get('lastName','') or ''}".strip()
            or op.get("name")
            or "Sem nome"
        )
        telefone = contato.get("phone") or ""

        notas = notas_por_contato.get(cid, []) or []
        notas_na_janela = []
        for n in notas:
            d = parse_iso_utc(n.get("dateAdded", ""))
            if d and inicio_utc <= d <= fim_utc:
                notas_na_janela.append((d, n))
        notas_na_janela.sort(key=lambda x: x[0], reverse=True)

        if notas_na_janela:
            ultimo = notas_na_janela[0][1]
            obs_txt = strip_html(ultimo.get("bodyText") or ultimo.get("body") or "")
            obs = truncar(obs_txt, 150) if obs_txt else "Sem observacao no periodo"
        else:
            obs = "Sem observacao no periodo"

        stage_nome = STAGES.get(op.get("pipelineStageId"), "DESCONHECIDO")
        if op.get("status") == "lost":
            stage_nome = "PERDIDO"
        elif op.get("status") == "won":
            stage_nome = "GANHAMOS"

        created = parse_iso_utc(op.get("createdAt", ""))
        data_entrada_br = (
            created.astimezone(TZ_BR).strftime("%d/%m/%Y %H:%M") if created else "-"
        )

        linhas.append(
            {
                "id": op.get("id"),
                "nome": nome,
                "telefone": telefone,
                "stage": stage_nome,
                "data_entrada": data_entrada_br,
                "data_entrada_sort": created.timestamp() if created else 0,
                "obs": obs,
            }
        )

    # Ordenar: por stage (ordem definida) + por data desc
    stage_idx = {s: i for i, s in enumerate(STAGE_ORDER)}
    linhas.sort(
        key=lambda x: (stage_idx.get(x["stage"], 99), -x["data_entrada_sort"])
    )

    return {
        "contagem_stage": contagem_stage,
        "total": total,
        "novos_total": novos_total,
        "qualificados": qualificados,
        "fechamentos": fechamentos,
        "perdidos": perdidos,
        "taxa_qual": taxa_qual,
        "taxa_fech": taxa_fech,
        "linhas": linhas,
    }


# ============================================================
# Render HTML
# ============================================================
def render_html(agg: dict, periodo_label: str, inicio_br: datetime, fim_br: datetime) -> str:
    gerado_em = datetime.now(TZ_BR).strftime("%d/%m/%Y %H:%M")
    periodo_curto = f"{inicio_br.strftime('%d/%m')} a {fim_br.strftime('%d/%m/%Y')}"

    total_stages = sum(agg["contagem_stage"].values()) or 1
    linhas_stage = []
    for nome in STAGE_ORDER:
        qtd = agg["contagem_stage"].get(nome, 0)
        pct = qtd / total_stages * 100
        cor = STAGE_COLORS.get(nome, "#64748b")
        linhas_stage.append(
            f"""
            <tr>
                <td><span class="stage-tag" style="background:{cor}22;color:{cor};border:1px solid {cor}55">{html.escape(nome)}</span></td>
                <td class="num">{qtd}</td>
                <td>
                    <div class="bar-wrap">
                        <div class="bar" style="width:{pct:.1f}%;background:{cor}"></div>
                        <span class="bar-label">{pct:.1f}%</span>
                    </div>
                </td>
            </tr>
            """
        )

    linhas_leads = []
    for i, l in enumerate(agg["linhas"], 1):
        cor = STAGE_COLORS.get(l["stage"], "#64748b")
        tel_clean = re.sub(r"\D", "", l["telefone"] or "")
        tel_link = (
            f'<a href="https://wa.me/{tel_clean}" target="_blank" class="phone">{html.escape(l["telefone"])}</a>'
            if tel_clean
            else '<span class="muted">-</span>'
        )
        linhas_leads.append(
            f"""
            <tr>
                <td class="num muted">{i}</td>
                <td><strong>{html.escape(l['nome'])}</strong></td>
                <td>{tel_link}</td>
                <td><span class="stage-tag" style="background:{cor}22;color:{cor};border:1px solid {cor}55">{html.escape(l['stage'])}</span></td>
                <td class="muted">{html.escape(l['data_entrada'])}</td>
                <td class="obs">{html.escape(l['obs'])}</td>
            </tr>
            """
        )

    if not linhas_leads:
        linhas_leads.append(
            '<tr><td colspan="6" class="muted" style="text-align:center;padding:32px">Nenhum lead com atividade no periodo.</td></tr>'
        )

    html_out = f"""<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Relatorio Semanal PX3 Lab — {periodo_curto}</title>
<style>
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
body {{
    font-family: -apple-system, 'Segoe UI', Roboto, 'Inter', sans-serif;
    background: linear-gradient(135deg, #0a0e1a 0%, #111827 100%);
    color: #e5e7eb;
    line-height: 1.5;
    min-height: 100vh;
    padding: 32px 16px;
}}
.container {{
    max-width: 1240px;
    margin: 0 auto;
}}
header {{
    text-align: center;
    margin-bottom: 40px;
    padding-bottom: 32px;
    border-bottom: 1px solid #1f2937;
}}
.brand {{
    display: inline-flex;
    align-items: center;
    gap: 12px;
    padding: 8px 18px;
    background: linear-gradient(135deg, #6366f1, #ec4899);
    border-radius: 999px;
    font-weight: 700;
    font-size: 12px;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: white;
    margin-bottom: 18px;
}}
h1 {{
    font-size: 36px;
    font-weight: 800;
    background: linear-gradient(135deg, #fff 0%, #94a3b8 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    margin-bottom: 8px;
}}
.subtitle {{
    color: #94a3b8;
    font-size: 16px;
    margin-bottom: 4px;
}}
.generated {{
    color: #64748b;
    font-size: 13px;
    margin-top: 8px;
}}
.cards {{
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 16px;
    margin-bottom: 40px;
}}
.card {{
    background: rgba(30, 41, 59, 0.5);
    backdrop-filter: blur(12px);
    border: 1px solid rgba(99, 102, 241, 0.15);
    border-radius: 16px;
    padding: 22px;
    transition: transform .2s, border-color .2s;
}}
.card:hover {{
    transform: translateY(-2px);
    border-color: rgba(99, 102, 241, 0.45);
}}
.card .label {{
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    color: #94a3b8;
    font-weight: 600;
    margin-bottom: 8px;
}}
.card .value {{
    font-size: 36px;
    font-weight: 800;
    color: #fff;
    line-height: 1.1;
}}
.card .sub {{
    font-size: 12px;
    color: #64748b;
    margin-top: 4px;
}}
.card.accent-blue .value {{ color: #60a5fa; }}
.card.accent-purple .value {{ color: #a78bfa; }}
.card.accent-green .value {{ color: #34d399; }}
.card.accent-red .value {{ color: #f87171; }}
.card.accent-pink .value {{ color: #f472b6; }}

section {{
    background: rgba(30, 41, 59, 0.4);
    border: 1px solid rgba(99, 102, 241, 0.15);
    border-radius: 16px;
    padding: 28px;
    margin-bottom: 28px;
}}
section h2 {{
    font-size: 20px;
    font-weight: 700;
    color: #fff;
    margin-bottom: 6px;
    display: flex;
    align-items: center;
    gap: 10px;
}}
section h2::before {{
    content: '';
    width: 4px;
    height: 22px;
    background: linear-gradient(180deg, #6366f1, #ec4899);
    border-radius: 4px;
}}
section .desc {{
    color: #94a3b8;
    font-size: 13px;
    margin-bottom: 20px;
}}
table {{
    width: 100%;
    border-collapse: collapse;
}}
thead th {{
    text-align: left;
    padding: 12px 14px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: #94a3b8;
    font-weight: 600;
    border-bottom: 1px solid #1f2937;
    background: rgba(15, 23, 42, 0.5);
}}
tbody td {{
    padding: 14px;
    border-bottom: 1px solid #1f293755;
    font-size: 14px;
    vertical-align: top;
}}
tbody tr:hover {{ background: rgba(99, 102, 241, 0.05); }}
.num {{ text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }}
.muted {{ color: #64748b; }}
.stage-tag {{
    display: inline-block;
    padding: 4px 10px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
}}
.bar-wrap {{
    position: relative;
    background: rgba(15, 23, 42, 0.7);
    border-radius: 8px;
    height: 24px;
    overflow: hidden;
    border: 1px solid #1f2937;
}}
.bar {{
    height: 100%;
    border-radius: 8px;
    transition: width .6s ease;
}}
.bar-label {{
    position: absolute;
    right: 10px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 12px;
    font-weight: 600;
    color: #fff;
    text-shadow: 0 1px 2px rgba(0,0,0,.6);
}}
.phone {{
    color: #34d399;
    text-decoration: none;
    font-variant-numeric: tabular-nums;
}}
.phone:hover {{ text-decoration: underline; }}
.obs {{
    color: #cbd5e1;
    font-size: 13px;
    max-width: 380px;
}}
footer {{
    text-align: center;
    margin-top: 40px;
    padding-top: 24px;
    border-top: 1px solid #1f2937;
    color: #64748b;
    font-size: 12px;
}}
@media (max-width: 768px) {{
    h1 {{ font-size: 24px; }}
    .card .value {{ font-size: 28px; }}
    section {{ padding: 18px; }}
    .obs {{ max-width: 200px; }}
    table {{ font-size: 12px; }}
    thead th, tbody td {{ padding: 8px; }}
}}
</style>
</head>
<body>
<div class="container">
    <header>
        <div class="brand">📊 PX3 LAB · CRM PADRAO PX3</div>
        <h1>Relatorio Semanal</h1>
        <div class="subtitle">{periodo_label}</div>
        <div class="generated">Gerado em {gerado_em}</div>
    </header>

    <div class="cards">
        <div class="card accent-blue">
            <div class="label">Novos leads</div>
            <div class="value">{agg['novos_total']}</div>
            <div class="sub">criados na semana</div>
        </div>
        <div class="card accent-purple">
            <div class="label">Qualificados</div>
            <div class="value">{agg['qualificados']}</div>
            <div class="sub">qual+teste+prop+ganhamos</div>
        </div>
        <div class="card accent-green">
            <div class="label">Ganhamos</div>
            <div class="value">{agg['fechamentos']}</div>
            <div class="sub">fechamentos</div>
        </div>
        <div class="card accent-red">
            <div class="label">Perdidos</div>
            <div class="value">{agg['perdidos']}</div>
            <div class="sub">no periodo</div>
        </div>
        <div class="card accent-pink">
            <div class="label">Taxa Qualificacao</div>
            <div class="value">{agg['taxa_qual']:.1f}%</div>
            <div class="sub">qualificados/novos</div>
        </div>
        <div class="card">
            <div class="label">Atividade Total</div>
            <div class="value">{agg['total']}</div>
            <div class="sub">opps c/ movimento</div>
        </div>
    </div>

    <section>
        <h2>Distribuicao por Stage</h2>
        <p class="desc">Distribuicao das oportunidades com atividade na semana, agrupadas pelo stage ATUAL.</p>
        <table>
            <thead>
                <tr><th>Stage</th><th class="num">Qtd</th><th style="width:60%">% do total</th></tr>
            </thead>
            <tbody>
                {''.join(linhas_stage)}
            </tbody>
        </table>
    </section>

    <section>
        <h2>Leads Individuais</h2>
        <p class="desc">Todos os leads com criacao ou movimentacao no periodo, ordenados pelo funil. Clique no telefone para abrir o WhatsApp.</p>
        <table>
            <thead>
                <tr>
                    <th>#</th>
                    <th>Nome</th>
                    <th>Telefone</th>
                    <th>Stage atual</th>
                    <th>Entrada</th>
                    <th>Observacao mais recente</th>
                </tr>
            </thead>
            <tbody>
                {''.join(linhas_leads)}
            </tbody>
        </table>
    </section>

    <footer>
        Relatorio gerado automaticamente pelo agente Mia · Agencia Climb Digital<br>
        Pipeline: PADRAO PX3 · Fuso: America/Sao_Paulo
    </footer>
</div>
</body>
</html>
"""
    return html_out


# ============================================================
# Telegram outbox
# ============================================================
def enviar_telegram(html_path: Path, link_publico: str | None, agg: dict, periodo_label: str):
    outbox = Path("/opt/mia-bot/outbox")
    if not outbox.exists():
        print("[!] outbox nao existe, pulando envio Telegram", flush=True)
        return

    texto = (
        f"📊 *Relatorio Semanal PX3 Lab*\n\n"
        f"📅 {periodo_label}\n\n"
        f"👥 Novos leads: *{agg['novos_total']}*\n"
        f"✅ Qualificados: *{agg['qualificados']}*\n"
        f"🏆 Ganhamos: *{agg['fechamentos']}*\n"
        f"❌ Perdidos: *{agg['perdidos']}*\n"
        f"📈 Taxa qualificacao: *{agg['taxa_qual']:.1f}%*\n"
    )
    if link_publico:
        texto += f"\n🔗 {link_publico}"

    ts = int(time.time() * 1e9)
    msg_path = outbox / f"{ts}.json"
    with open(msg_path, "w") as f:
        json.dump({"text": texto}, f)

    # Documento (HTML)
    ts2 = ts + 1
    doc_path = outbox / f"{ts2}.json"
    with open(doc_path, "w") as f:
        json.dump(
            {"document": str(html_path), "caption": "📎 Relatorio HTML completo"}, f
        )
    print(f"[ok] mensagens criadas no outbox: {msg_path.name}, {doc_path.name}", flush=True)


# ============================================================
# Main
# ============================================================
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--semana",
        help="Semana ISO (YYYY-WW), ex: 2026-25. Default: semana atual.",
    )
    parser.add_argument(
        "--enviar",
        action="store_true",
        help="Envia via Telegram outbox apos gerar.",
    )
    parser.add_argument(
        "--link",
        help="Link publico do HTML (para incluir na msg do Telegram).",
    )
    parser.add_argument(
        "--saida",
        default=str(SCRIPT_DIR / "relatorio_atual.html"),
        help="Path do HTML de saida.",
    )
    args = parser.parse_args()

    inicio_utc, fim_utc, label, inicio_br, fim_br = calcular_janela(args.semana)
    print(f"[*] Janela: {label}", flush=True)
    print(f"    UTC:  {inicio_utc.isoformat()} -> {fim_utc.isoformat()}", flush=True)

    dados = coletar_dados(inicio_utc, fim_utc)
    agg = agregar(dados, inicio_utc, fim_utc)

    html_str = render_html(agg, label, inicio_br, fim_br)
    out_path = Path(args.saida)
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(html_str, encoding="utf-8")
    print(f"[ok] HTML salvo em: {out_path}", flush=True)

    print(
        f"[stats] total={agg['total']} novos={agg['novos_total']} "
        f"qualif={agg['qualificados']} ganhamos={agg['fechamentos']} perdidos={agg['perdidos']}",
        flush=True,
    )

    if args.enviar:
        enviar_telegram(out_path, args.link, agg, label)


if __name__ == "__main__":
    main()
