#!/usr/bin/env python3
"""
Hotmart -> Meta Custom Audience
Busca compradores do produto Passo a Passo da Radiestesia na Prática (Hotmart)
e sobe como Custom Audience no Meta Ads, gerando também Lookalike BR 1%.

Estratégia (v2):
1. Tenta listar as ofertas do produto e itera por cada offer_code.
2. Fallback: busca TODAS as vendas da conta sem product_id nem start_date,
   e filtra client-side por product.id == PRODUCT_ID.
3. Diagnóstico: se tudo resultar em poucas vendas, dumpa os campos da resposta.
"""

import hashlib
import json
import os
import sys
import time
from datetime import datetime
from pathlib import Path

import requests

# ==========================
# CONFIG
# ==========================
HOTMART_CLIENT_ID = "089991ef-4eee-40f3-b72f-3d8d0299b763"
HOTMART_CLIENT_SECRET = "946a2012-5ac0-405e-92d9-97a61466da35"
HOTMART_BASIC = "Basic MDg5OTkxZWYtNGVlZS00MGYzLWI3MmYtM2Q4ZDAyOTliNzYzOjk0NmEyMDEyLTVhYzAtNDA1ZS05MmQ5LTk3YTYxNDY2ZGEzNQ=="

HOTMART_AUTH_URL = "https://api-sec-vlc.hotmart.com/security/oauth/token"
HOTMART_BASE = "https://developers.hotmart.com/payments/api/v1"
HOTMART_SALES_URL = f"{HOTMART_BASE}/sales/history"
HOTMART_PRODUCT_PRICE_URL = f"{HOTMART_BASE}/products/{{product_id}}/price"
HOTMART_OFFERS_URL = f"{HOTMART_BASE}/sales/offers"

PRODUCT_ID = "1437935"

META_TOKEN_PATH = "/opt/mia/config/meta_token_renato.txt"
META_ACCOUNT_ID = "517468148842987"
META_API_VERSION = "v21.0"

OUTPUT_DIR = Path("/opt/mia/workspace/clientes/borrello")
AUDIENCE_ID_FILE = OUTPUT_DIR / "audience_compradores.txt"
LAL_ID_FILE = OUTPUT_DIR / "audience_lal_compradores.txt"
BUYERS_JSON = OUTPUT_DIR / "compradores_radiestesia.json"
DIAG_JSON = OUTPUT_DIR / "hotmart_diagnostico.json"

OUTBOX_DIR = "/opt/mia-bot/outbox"


# ==========================
# HELPERS
# ==========================
def log(msg: str):
    ts = datetime.now().strftime("%H:%M:%S")
    print(f"[{ts}] {msg}", flush=True)


def hash_meta(valor: str) -> str:
    if not valor:
        return ""
    return hashlib.sha256(valor.strip().lower().encode("utf-8")).hexdigest()


def send_outbox(text: str):
    try:
        os.makedirs(OUTBOX_DIR, exist_ok=True)
        fname = f"{OUTBOX_DIR}/{int(time.time() * 1e9)}.json"
        with open(fname, "w", encoding="utf-8") as f:
            json.dump({"text": text}, f, ensure_ascii=False)
    except Exception as e:
        log(f"Falha ao enviar outbox: {e}")


# ==========================
# HOTMART
# ==========================
def hotmart_token() -> str:
    log("Autenticando na Hotmart...")
    r = requests.post(
        HOTMART_AUTH_URL,
        headers={"Authorization": HOTMART_BASIC},
        params={
            "grant_type": "client_credentials",
            "client_id": HOTMART_CLIENT_ID,
            "client_secret": HOTMART_CLIENT_SECRET,
        },
        timeout=30,
    )
    r.raise_for_status()
    tok = r.json().get("access_token")
    if not tok:
        raise RuntimeError(f"Sem access_token: {r.text}")
    log("Auth Hotmart OK.")
    return tok


def hotmart_list_offers(token: str):
    """Tenta listar todas as ofertas do produto via múltiplos endpoints."""
    headers = {"Authorization": f"Bearer {token}"}
    offers = []

    # Tentativa 1: /products/{id}/price
    url = HOTMART_PRODUCT_PRICE_URL.format(product_id=PRODUCT_ID)
    try:
        log(f"Tentando listar ofertas via {url}")
        r = requests.get(url, headers=headers, timeout=30)
        log(f"  status={r.status_code}")
        if r.status_code == 200:
            data = r.json()
            log(f"  resposta: {json.dumps(data)[:500]}")
            # Formatos comuns: {"items":[{...}]} ou lista direta
            items = data.get("items") if isinstance(data, dict) else data
            if items:
                for it in items:
                    code = it.get("offer_code") or it.get("code") or it.get("key")
                    if code:
                        offers.append(code)
        else:
            log(f"  body: {r.text[:300]}")
    except Exception as e:
        log(f"  falhou: {e}")

    # Tentativa 2: /sales/offers?product_id=
    if not offers:
        try:
            log(f"Tentando listar ofertas via {HOTMART_OFFERS_URL}?product_id={PRODUCT_ID}")
            r = requests.get(
                HOTMART_OFFERS_URL,
                headers=headers,
                params={"product_id": PRODUCT_ID},
                timeout=30,
            )
            log(f"  status={r.status_code}")
            if r.status_code == 200:
                data = r.json()
                log(f"  resposta: {json.dumps(data)[:500]}")
                items = data.get("items") if isinstance(data, dict) else data
                if items:
                    for it in items:
                        code = it.get("offer_code") or it.get("code") or it.get("key")
                        if code:
                            offers.append(code)
            else:
                log(f"  body: {r.text[:300]}")
        except Exception as e:
            log(f"  falhou: {e}")

    offers = list(dict.fromkeys(offers))  # unique preservando ordem
    log(f"Ofertas descobertas: {len(offers)} -> {offers}")
    return offers


def _extract_buyer(it: dict):
    """Extrai (email, name, ucode) do item de venda em qualquer formato conhecido."""
    buyer = it.get("buyer") or {}
    email = (buyer.get("email") or "").strip().lower()
    name = (buyer.get("name") or "").strip()
    ucode = buyer.get("ucode")
    return email, name, ucode


def _extract_product_id(it: dict):
    """Extrai product_id do item de venda em qualquer formato."""
    prod = it.get("product") or {}
    pid = prod.get("id") or it.get("product_id")
    if pid is not None:
        return str(pid)
    return None


def _fetch_page(token: str, params: dict):
    """Faz GET em sales/history e retorna (items, next_page_token, raw_response)."""
    headers = {"Authorization": f"Bearer {token}"}
    r = requests.get(HOTMART_SALES_URL, headers=headers, params=params, timeout=60)
    if r.status_code != 200:
        log(f"  Erro Hotmart {r.status_code}: {r.text[:400]}")
        return [], None, None
    data = r.json()
    items = data.get("items", [])
    page_info = data.get("page_info") or {}
    next_tok = page_info.get("next_page_token")
    return items, next_tok, data


def fetch_by_offer(token: str, offer_code: str, buyers: dict, diag: dict):
    """Busca vendas de uma oferta específica em COMPLETE + APPROVED."""
    for status in ("COMPLETE", "APPROVED"):
        page = 0
        page_token = None
        total = 0
        while True:
            page += 1
            params = {
                "max_results": 500,
                "transaction_status": status,
                "product_id": PRODUCT_ID,
                "offer_code": offer_code,
            }
            if page_token:
                params["page_token"] = page_token

            log(f"  [offer={offer_code}] [{status}] pág {page}")
            items, next_tok, _ = _fetch_page(token, params)
            total += len(items)
            for it in items:
                email, name, ucode = _extract_buyer(it)
                if email and email not in buyers:
                    buyers[email] = {"email": email, "name": name, "ucode": ucode}
            page_token = next_tok
            if not page_token:
                break
        log(f"  [offer={offer_code}] [{status}] total: {total}")
        diag.setdefault("por_oferta", {}).setdefault(offer_code, {})[status] = total


def fetch_by_product(token: str, buyers: dict, diag: dict):
    """Busca por product_id sem offer_code e sem start_date."""
    for status in ("COMPLETE", "APPROVED"):
        page = 0
        page_token = None
        total = 0
        while True:
            page += 1
            params = {
                "max_results": 500,
                "transaction_status": status,
                "product_id": PRODUCT_ID,
            }
            if page_token:
                params["page_token"] = page_token

            log(f"  [product_id={PRODUCT_ID}] [{status}] pág {page}")
            items, next_tok, _ = _fetch_page(token, params)
            total += len(items)
            for it in items:
                email, name, ucode = _extract_buyer(it)
                if email and email not in buyers:
                    buyers[email] = {"email": email, "name": name, "ucode": ucode}
            page_token = next_tok
            if not page_token:
                break
        log(f"  [product_id direto] [{status}] total: {total}")
        diag.setdefault("por_product_id", {})[status] = total


def fetch_all_and_filter(token: str, buyers: dict, diag: dict, sample_items: list):
    """Busca TODAS as vendas da conta sem filtro e filtra client-side."""
    total_conta = 0
    total_produto = 0
    for status in ("COMPLETE", "APPROVED"):
        page = 0
        page_token = None
        status_total = 0
        status_produto = 0
        while True:
            page += 1
            params = {
                "max_results": 500,
                "transaction_status": status,
            }
            if page_token:
                params["page_token"] = page_token

            log(f"  [SEM FILTRO] [{status}] pág {page}")
            items, next_tok, raw = _fetch_page(token, params)
            status_total += len(items)

            for it in items:
                # Coleta amostra dos primeiros 3 itens (todos os campos) pra diagnóstico
                if len(sample_items) < 3:
                    sample_items.append(it)

                pid = _extract_product_id(it)
                if pid == PRODUCT_ID:
                    status_produto += 1
                    email, name, ucode = _extract_buyer(it)
                    if email and email not in buyers:
                        buyers[email] = {"email": email, "name": name, "ucode": ucode}

            page_token = next_tok
            if not page_token:
                break

            # Safety: se a conta tiver dezenas de milhares, evita loop infinito no diagnóstico
            if page >= 200:
                log(f"  [SEM FILTRO] [{status}] atingiu 200 páginas, parando")
                break

        log(f"  [SEM FILTRO] [{status}] total conta: {status_total}, do produto: {status_produto}")
        diag.setdefault("sem_filtro", {})[status] = {
            "total_conta": status_total,
            "do_produto": status_produto,
        }
        total_conta += status_total
        total_produto += status_produto

    log(f"[SEM FILTRO] TOTAL: {total_conta} vendas na conta, {total_produto} do produto {PRODUCT_ID}")


def hotmart_fetch_buyers(token: str):
    """Fluxo completo: ofertas -> product_id direto -> tudo sem filtro."""
    buyers = {}
    diag = {}
    sample_items = []

    # Passo 1: descobrir ofertas
    offers = hotmart_list_offers(token)
    diag["ofertas_descobertas"] = offers

    # Passo 2: iterar por cada oferta (se houver)
    if offers:
        log(f"Buscando vendas por cada uma das {len(offers)} ofertas...")
        for oc in offers:
            fetch_by_offer(token, oc, buyers, diag)
        log(f"Após ofertas: {len(buyers)} compradores únicos")

    # Passo 3: passada por product_id sem start_date (garante que pegou tudo)
    log("Passada por product_id direto (sem start_date)...")
    fetch_by_product(token, buyers, diag)
    log(f"Após product_id direto: {len(buyers)} compradores únicos")

    # Passo 4: se ainda tá com poucos, busca tudo e filtra client-side
    if len(buyers) < 100:
        log(f"Poucos compradores ({len(buyers)}). Buscando TUDO da conta pra filtrar client-side...")
        fetch_all_and_filter(token, buyers, diag, sample_items)
        log(f"Após busca full: {len(buyers)} compradores únicos")

    # Grava diagnóstico
    diag["total_compradores_unicos"] = len(buyers)
    diag["sample_items"] = sample_items
    with open(DIAG_JSON, "w", encoding="utf-8") as f:
        json.dump(diag, f, ensure_ascii=False, indent=2, default=str)
    log(f"Diagnóstico salvo em {DIAG_JSON}")

    return list(buyers.values()), diag, sample_items


# ==========================
# META
# ==========================
def meta_token() -> str:
    with open(META_TOKEN_PATH, "r") as f:
        return f.read().strip()


def meta_create_custom_audience(token: str) -> str:
    log("Criando Custom Audience no Meta...")
    r = requests.post(
        f"https://graph.facebook.com/{META_API_VERSION}/act_{META_ACCOUNT_ID}/customaudiences",
        params={"access_token": token},
        json={
            "name": "Compradores Radiestesia Prática - Hotmart",
            "subtype": "CUSTOM",
            "description": "Lista de compradores do curso Passo a Passo da Radiestesia na Prática (Hotmart)",
            "customer_file_source": "USER_PROVIDED_ONLY",
        },
        timeout=60,
    )
    if r.status_code >= 300:
        log(f"Erro criar audience: {r.status_code} {r.text}")
        r.raise_for_status()
    aid = r.json()["id"]
    log(f"Custom Audience criada: {aid}")
    return aid


def meta_upload_users(token: str, audience_id: str, buyers: list) -> int:
    log(f"Fazendo upload de {len(buyers)} usuários em lotes de 10k...")
    schema = "EMAIL_SHA256"
    total = 0
    batch_size = 10000
    session_id = int(time.time())
    total_batches = (len(buyers) + batch_size - 1) // batch_size

    for i in range(0, len(buyers), batch_size):
        chunk = buyers[i : i + batch_size]
        batch_num = i // batch_size + 1
        data = [[hash_meta(b["email"])] for b in chunk]

        payload = {
            "schema": [schema] if isinstance(schema, str) else schema,
            "data": data,
        }

        body = {
            "payload": payload,
            "session": {
                "session_id": session_id,
                "batch_seq": batch_num,
                "last_batch_flag": batch_num == total_batches,
                "estimated_num_total": len(buyers),
            },
        }

        r = requests.post(
            f"https://graph.facebook.com/{META_API_VERSION}/{audience_id}/users",
            params={"access_token": token},
            json=body,
            timeout=120,
        )
        if r.status_code >= 300:
            log(f"Erro upload batch {batch_num}: {r.status_code} {r.text[:500]}")
            r.raise_for_status()

        resp = r.json()
        received = resp.get("num_received", len(chunk))
        invalid = resp.get("num_invalid_entries", 0)
        total += received
        log(f"  Batch {batch_num}/{total_batches}: recebidos={received} inválidos={invalid}")

    log(f"Upload concluído. Total enviado: {total}")
    return total


def meta_create_lookalike(token: str, source_audience_id: str) -> str:
    log("Criando Lookalike BR 1%...")
    r = requests.post(
        f"https://graph.facebook.com/{META_API_VERSION}/act_{META_ACCOUNT_ID}/customaudiences",
        params={"access_token": token},
        json={
            "name": "LAL Compradores Radiestesia - BR 1%",
            "subtype": "LOOKALIKE",
            "origin_audience_id": source_audience_id,
            "lookalike_spec": json.dumps({
                "type": "similarity",
                "starting_ratio": 0.0,
                "ratio": 0.01,
                "country": "BR",
            }),
        },
        timeout=60,
    )
    if r.status_code >= 300:
        log(f"Erro criar LAL: {r.status_code} {r.text}")
        r.raise_for_status()
    lid = r.json()["id"]
    log(f"Lookalike criada: {lid}")
    return lid


# ==========================
# MAIN
# ==========================
def main():
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

    # 1. Hotmart
    ht_token = hotmart_token()
    buyers, diag, sample_items = hotmart_fetch_buyers(ht_token)

    if not buyers:
        msg = "Nenhum comprador encontrado no Hotmart para o produto 1437935."
        log(msg)
        send_outbox(f"⚠️ {msg}\n\nDiagnóstico: {json.dumps(diag, ensure_ascii=False)[:1500]}")
        sys.exit(1)

    # Salva backup local
    with open(BUYERS_JSON, "w", encoding="utf-8") as f:
        json.dump(buyers, f, ensure_ascii=False, indent=2)
    log(f"Backup salvo em {BUYERS_JSON}")

    # Se ainda encontrou apenas 3 ou menos, mandar diagnóstico e sair
    if len(buyers) <= 3:
        campos = list(sample_items[0].keys()) if sample_items else []
        msg = (
            f"⚠️ Ainda apenas {len(buyers)} compradores encontrados após tentar todas as estratégias.\n\n"
            f"Diagnóstico:\n"
            f"• Ofertas descobertas: {diag.get('ofertas_descobertas', [])}\n"
            f"• Por oferta: {diag.get('por_oferta', {})}\n"
            f"• Por product_id direto: {diag.get('por_product_id', {})}\n"
            f"• Sem filtro (total conta): {diag.get('sem_filtro', {})}\n"
            f"• Campos de um item de venda: {campos}\n\n"
            f"Diag salvo em {DIAG_JSON}"
        )
        send_outbox(msg)
        log(msg)
        sys.exit(1)

    # 2. Meta - reaproveita audience existente
    m_token = meta_token()
    if AUDIENCE_ID_FILE.exists() and AUDIENCE_ID_FILE.read_text().strip():
        audience_id = AUDIENCE_ID_FILE.read_text().strip()
        log(f"Reaproveitando audience existente: {audience_id} (novo upload acumulativo)")
    else:
        audience_id = meta_create_custom_audience(m_token)
        AUDIENCE_ID_FILE.write_text(audience_id)

    total_sent = meta_upload_users(m_token, audience_id, buyers)

    # LAL
    lal_id = None
    if LAL_ID_FILE.exists() and LAL_ID_FILE.read_text().strip():
        lal_id = LAL_ID_FILE.read_text().strip()
        log(f"Lookalike já existente: {lal_id} (não recriando)")
    else:
        try:
            lal_id = meta_create_lookalike(m_token, audience_id)
            LAL_ID_FILE.write_text(lal_id)
        except Exception as e:
            log(f"Falha ao criar LAL: {e}")

    # 3. Report
    msg = (
        "✅ *Audience atualizada — lista completa*\n\n"
        f"• Total de compradores subidos: {len(buyers)}\n"
        f"• Usuários enviados ao Meta: {total_sent}\n"
        f"• Audience ID: `{audience_id}`\n"
    )
    if lal_id:
        msg += f"• Lookalike BR 1%: `{lal_id}`\n"
    msg += f"\nOfertas descobertas: {len(diag.get('ofertas_descobertas', []))}\n"
    msg += f"Com {len(buyers)} pessoas na base, o Lookalike agora tem musculatura pra rodar."

    send_outbox(msg)
    log("Fim.")


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        err = f"❌ Erro no hotmart_to_meta_audience: {e}"
        log(err)
        send_outbox(err)
        sys.exit(1)
