#!/usr/bin/env python3
"""
CSV Hotmart (8 arquivos 2019-2026) -> Meta Custom Audiences
Cria 2 audiences + 2 lookalikes BR 1%:
  1. Base completa de alunos (todos os emails únicos)
  2. Mesas Radiônicas (filtro por Nome do Produto contendo 'radiônica' ou 'mes')
"""

import csv
import glob
import hashlib
import json
import os
import re
import sys
import time
import unicodedata
from datetime import datetime
from pathlib import Path

import requests

# ==========================
# CONFIG
# ==========================
CSV_DIR = "/tmp/borrello_leads"
CSV_GLOB = f"{CSV_DIR}/sales_history_*.csv"

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")
OUTBOX_DIR = "/opt/mia-bot/outbox"

# Arquivos de saída (IDs)
BASE_AUD_FILE = OUTPUT_DIR / "audience_base_completa.txt"
BASE_LAL_FILE = OUTPUT_DIR / "audience_lal_base_completa.txt"
MESAS_AUD_FILE = OUTPUT_DIR / "audience_mesas_radionicas.txt"
MESAS_LAL_FILE = OUTPUT_DIR / "audience_lal_mesas_radionicas.txt"

# Backups
BASE_EMAILS_JSON = OUTPUT_DIR / "emails_base_completa.json"
MESAS_EMAILS_JSON = OUTPUT_DIR / "emails_mesas_radionicas.json"

STATUS_VALIDOS = {"completo", "aprovado", "complete", "approved"}


# ==========================
# 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 normalize(s: str) -> str:
    if not s:
        return ""
    nfkd = unicodedata.normalize("NFKD", s)
    return "".join(c for c in nfkd if not unicodedata.combining(c)).lower()


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}")


EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")


def is_valid_email(e: str) -> bool:
    return bool(EMAIL_RE.match(e))


# ==========================
# CSV
# ==========================
def parse_csvs():
    """
    Lê todos os CSVs, retorna:
      - base_emails: set com todos os emails únicos válidos (status válido)
      - mesas_emails: set com emails cujo produto contém 'radionica' ou 'mes'
      - stats: dict com contadores por arquivo
    """
    base_emails = set()
    mesas_emails = set()
    stats = {}
    produtos_mesas = set()

    files = sorted(glob.glob(CSV_GLOB))
    log(f"Encontrados {len(files)} CSVs")

    for fpath in files:
        fname = os.path.basename(fpath)
        linhas = 0
        emails_arquivo = 0
        mesas_arquivo = 0
        status_ignorados = 0

        with open(fpath, "r", encoding="utf-8-sig", newline="") as f:
            reader = csv.DictReader(f, delimiter=";")
            for row in reader:
                linhas += 1
                status_raw = (row.get("Status") or "").strip()
                status_norm = normalize(status_raw)
                if status_norm not in STATUS_VALIDOS:
                    status_ignorados += 1
                    continue

                email = (row.get("Email") or "").strip().lower()
                if not email or not is_valid_email(email):
                    continue

                produto = (row.get("Nome do Produto") or "").strip()
                produto_norm = normalize(produto)

                base_emails.add(email)
                emails_arquivo += 1

                # Filtro Mesas Radiônicas
                if "radionica" in produto_norm or "mesa" in produto_norm:
                    mesas_emails.add(email)
                    mesas_arquivo += 1
                    produtos_mesas.add(produto)

        stats[fname] = {
            "linhas": linhas,
            "emails_validos_status_ok": emails_arquivo,
            "matches_mesas": mesas_arquivo,
            "status_ignorados": status_ignorados,
        }
        log(f"  {fname}: {linhas} linhas / {emails_arquivo} emails válidos / {mesas_arquivo} mesas / {status_ignorados} status ignorado")

    log(f"TOTAL: {len(base_emails)} emails únicos base, {len(mesas_emails)} mesas radiônicas")
    log(f"Produtos Mesas: {sorted(produtos_mesas)}")

    return sorted(base_emails), sorted(mesas_emails), stats, sorted(produtos_mesas)


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


def meta_create_custom_audience(token: str, name: str, description: str) -> str:
    log(f"Criando Custom Audience: {name}")
    r = requests.post(
        f"https://graph.facebook.com/{META_API_VERSION}/act_{META_ACCOUNT_ID}/customaudiences",
        params={"access_token": token},
        json={
            "name": name,
            "subtype": "CUSTOM",
            "description": description,
            "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_emails(token: str, audience_id: str, emails: list) -> dict:
    log(f"Upload {len(emails)} emails na audience {audience_id}")
    total_sent = 0
    total_invalid = 0
    batch_size = 10000
    session_id = int(time.time())
    total_batches = (len(emails) + batch_size - 1) // batch_size

    for i in range(0, len(emails), batch_size):
        chunk = emails[i : i + batch_size]
        batch_num = i // batch_size + 1
        data = [[hash_meta(e)] for e in chunk]

        body = {
            "payload": {
                "schema": ["EMAIL_SHA256"],
                "data": data,
            },
            "session": {
                "session_id": session_id,
                "batch_seq": batch_num,
                "last_batch_flag": batch_num == total_batches,
                "estimated_num_total": len(emails),
            },
        }

        r = requests.post(
            f"https://graph.facebook.com/{META_API_VERSION}/{audience_id}/users",
            params={"access_token": token},
            json=body,
            timeout=180,
        )
        if r.status_code >= 300:
            log(f"Erro 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_sent += received
        total_invalid += invalid
        log(f"  batch {batch_num}/{total_batches}: recebidos={received} inválidos={invalid}")

    return {"sent": total_sent, "invalid": total_invalid}


def meta_create_lookalike(token: str, source_audience_id: str, name: str) -> str:
    log(f"Criando LAL: {name}")
    r = requests.post(
        f"https://graph.facebook.com/{META_API_VERSION}/act_{META_ACCOUNT_ID}/customaudiences",
        params={"access_token": token},
        json={
            "name": name,
            "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"LAL criada: {lid}")
    return lid


def process_audience(m_token, emails, aud_name, aud_desc, aud_file, lal_name, lal_file, result_key):
    """Cria audience, faz upload e cria LAL. Retorna dict com IDs e stats."""
    result = {"emails_total": len(emails), "erros": []}

    # Reaproveita se já existir
    if aud_file.exists() and aud_file.read_text().strip():
        audience_id = aud_file.read_text().strip()
        log(f"[{result_key}] reaproveitando audience {audience_id}")
    else:
        try:
            audience_id = meta_create_custom_audience(m_token, aud_name, aud_desc)
            aud_file.write_text(audience_id)
        except Exception as e:
            result["erros"].append(f"criar audience: {e}")
            return result

    result["audience_id"] = audience_id

    # Upload
    try:
        upload = meta_upload_emails(m_token, audience_id, emails)
        result["enviados"] = upload["sent"]
        result["invalidos"] = upload["invalid"]
    except Exception as e:
        result["erros"].append(f"upload: {e}")

    # LAL
    if lal_file.exists() and lal_file.read_text().strip():
        lal_id = lal_file.read_text().strip()
        log(f"[{result_key}] LAL já existente {lal_id}")
    else:
        try:
            lal_id = meta_create_lookalike(m_token, audience_id, lal_name)
            lal_file.write_text(lal_id)
        except Exception as e:
            result["erros"].append(f"criar LAL: {e}")
            lal_id = None

    result["lal_id"] = lal_id
    return result


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

    # 1. Ler CSVs
    log("Iniciando parse dos CSVs...")
    base_emails, mesas_emails, stats, produtos_mesas = parse_csvs()

    # Backup
    with open(BASE_EMAILS_JSON, "w", encoding="utf-8") as f:
        json.dump({"total": len(base_emails), "emails": base_emails, "stats": stats}, f, ensure_ascii=False, indent=2)
    with open(MESAS_EMAILS_JSON, "w", encoding="utf-8") as f:
        json.dump({"total": len(mesas_emails), "emails": mesas_emails, "produtos": produtos_mesas}, f, ensure_ascii=False, indent=2)

    if not base_emails:
        send_outbox("Nenhum email encontrado nos CSVs. Abortando.")
        sys.exit(1)

    # 2. Meta
    m_token = meta_token()

    log("=== AUDIENCE 1: BASE COMPLETA ===")
    r_base = process_audience(
        m_token,
        base_emails,
        aud_name="Alunos Borrello - Base Completa 2019-2026",
        aud_desc="Todos os compradores Hotmart Francisco Borrello (2019-2026), status Completo/Aprovado.",
        aud_file=BASE_AUD_FILE,
        lal_name="LAL Alunos Borrello BR 1%",
        lal_file=BASE_LAL_FILE,
        result_key="BASE",
    )

    log("=== AUDIENCE 2: MESAS RADIONICAS ===")
    r_mesas = process_audience(
        m_token,
        mesas_emails,
        aud_name="Alunos Mesas Radiônicas - Borrello",
        aud_desc="Compradores dos produtos de Mesas Radiônicas / Radiestesia do Francisco Borrello (Hotmart 2019-2026).",
        aud_file=MESAS_AUD_FILE,
        lal_name="LAL Mesas Radiônicas BR 1%",
        lal_file=MESAS_LAL_FILE,
        result_key="MESAS",
    )

    # 3. Report
    msg = (
        "Audiences Meta criadas — Borrello (base histórica Hotmart 2019-2026)\n\n"
        "AUDIENCE 1 — Base Completa\n"
        f"• Nome: Alunos Borrello - Base Completa 2019-2026\n"
        f"• Emails únicos: {r_base['emails_total']}\n"
        f"• Enviados ao Meta: {r_base.get('enviados', '-')} (inválidos: {r_base.get('invalidos', '-')})\n"
        f"• Audience ID: {r_base.get('audience_id', '-')}\n"
        f"• LAL BR 1% ID: {r_base.get('lal_id', '-')}\n"
    )
    if r_base.get("erros"):
        msg += f"• ERROS: {r_base['erros']}\n"

    msg += (
        "\nAUDIENCE 2 — Mesas Radiônicas\n"
        f"• Nome: Alunos Mesas Radiônicas - Borrello\n"
        f"• Emails únicos: {r_mesas['emails_total']}\n"
        f"• Enviados ao Meta: {r_mesas.get('enviados', '-')} (inválidos: {r_mesas.get('invalidos', '-')})\n"
        f"• Audience ID: {r_mesas.get('audience_id', '-')}\n"
        f"• LAL BR 1% ID: {r_mesas.get('lal_id', '-')}\n"
    )
    if r_mesas.get("erros"):
        msg += f"• ERROS: {r_mesas['erros']}\n"

    msg += f"\nProdutos considerados como Mesas ({len(produtos_mesas)}):\n"
    for p in produtos_mesas:
        msg += f"  - {p}\n"

    send_outbox(msg)
    log("Fim.")


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        import traceback
        err = f"Erro no csv_to_meta_audiences: {e}\n{traceback.format_exc()[:1500]}"
        log(err)
        send_outbox(err)
        sys.exit(1)
