#!/usr/bin/env python3
"""
Meta CAPI - PX3 Lab (PageView + Purchase)
=========================================

Servico HTTP interno que envia eventos server-side pro pixel Meta
1509133033542161 (PX3 Lab / Cloud PhotoRF / Server PhotoRF).

Endpoints publicos:
  POST/GET  https://track-capi-px3.agentesclimb.us/pageview
  POST      https://track-capi-px3.agentesclimb.us/purchase
  POST      https://track-capi-px3.agentesclimb.us/lead
  POST      https://track-capi-px3.agentesclimb.us/contact
  GET       https://track-capi-px3.agentesclimb.us/health

Porta local: 8918  (Traefik -> 172.18.0.1:8918)

Fluxo /pageview:
  1. Recebe GET ou POST com fbclid, gclid, UTMs, client_ip, client_ua,
     event_source_url.
  2. Dedupe SQLite por fbclid (janela 1h).
  3. Monta payload CAPI padrao Meta (fbc, user_data, event_id).
  4. POST em graph.facebook.com/v22.0/{pixel}/events?access_token=...
  5. Marca (event_type=pageview, key=fbclid) na tabela events_sent.

Fluxo /purchase:
  1. Recebe POST JSON com email, phone, first_name, last_name, city,
     state, zip, country, value, currency, content_name, content_ids,
     num_items, event_id, fbclid (opcional), client_ip (opcional),
     client_user_agent (opcional).
  2. Dedupe por event_id (janela 7 dias) - o N8N garante unicidade
     via padrao vra-<execution_id>.
  3. Monta payload Meta Purchase com user_data hasheado SHA-256
     (em, ph, fn, ln, ct, st, zp, country) + custom_data (value,
     currency, content_*).
  4. Envia pra Graph API e grava dedupe.

Config: /opt/mia/config/meta_capi_px3.env
Log:    /opt/mia/logs/track_capi_px3.log
DB:     /opt/mia/workspace/clientes/px3lab/capi_pageview/dedupe.db
"""

from __future__ import annotations

import hashlib
import json
import logging
import os
import re
import sqlite3
import sys
import threading
import time
import unicodedata
from datetime import datetime, timezone
from logging.handlers import RotatingFileHandler
from pathlib import Path
from typing import Any

import requests
from flask import Flask, jsonify, request

BASE_DIR = Path(__file__).resolve().parent

# ---------------------------------------------------------------------------
# Logging (rotativo + stdout)
# ---------------------------------------------------------------------------
LOG_FILE = Path("/opt/mia/logs/track_capi_px3.log")
LOG_FILE.parent.mkdir(parents=True, exist_ok=True)

_fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
_file_handler = RotatingFileHandler(
    LOG_FILE, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8"
)
_file_handler.setFormatter(_fmt)
_stream_handler = logging.StreamHandler(sys.stdout)
_stream_handler.setFormatter(_fmt)
logging.basicConfig(level=logging.INFO, handlers=[_file_handler, _stream_handler])
log = logging.getLogger("capi_px3")

# ---------------------------------------------------------------------------
# Env loading (dotenv com fallback manual)
# ---------------------------------------------------------------------------
try:
    from dotenv import load_dotenv
    load_dotenv("/opt/mia/config/meta_capi_px3.env")
except ImportError:
    envfile = "/opt/mia/config/meta_capi_px3.env"
    if os.path.exists(envfile):
        with open(envfile, encoding="utf-8") as f:
            for raw in f:
                line = raw.strip()
                if not line or line.startswith("#") or "=" not in line:
                    continue
                k, v = line.split("=", 1)
                os.environ.setdefault(k.strip(), v.strip())

META_API_VERSION = os.environ.get("META_API_VERSION", "v22.0")
META_PIXEL_ID = os.environ.get("META_PIXEL_ID_PX3", "").strip()
META_TOKEN = os.environ.get("META_CAPI_TOKEN", "").strip()
META_TEST_EVENT_CODE = os.environ.get("META_TEST_EVENT_CODE", "").strip()

if not META_PIXEL_ID or not META_TOKEN:
    log.critical("Config incompleta: META_PIXEL_ID_PX3 ou META_CAPI_TOKEN vazio")
    sys.exit(1)

META_ENDPOINT = (
    f"https://graph.facebook.com/{META_API_VERSION}/{META_PIXEL_ID}/events"
)

# ---------------------------------------------------------------------------
# SQLite dedupe (multi-evento)
# ---------------------------------------------------------------------------
DB_PATH = BASE_DIR / "dedupe.db"

# TTL da JANELA de dedupe por tipo (skip se ja enviou dentro desse tempo).
DEDUPE_TTL_BY_TYPE = {
    "pageview": 60 * 60,               # 1h  - conforme spec original
    "purchase": 7 * 24 * 60 * 60,      # 7 dias - janela de atribuicao Meta
    "lead":     7 * 24 * 60 * 60,      # 7 dias - janela suficiente pra dedupe browser<>server
    "contact":  60 * 60,               # 1h  - clique no botao WhatsApp, janela curta
}
DEDUPE_TTL_DEFAULT = 60 * 60

# TTL da RETENCAO no DB (limpeza defensiva na boot).
CLEANUP_TTL_SECONDS = 30 * 24 * 60 * 60  # 30 dias

_db_lock = threading.Lock()


def _db():
    conn = sqlite3.connect(str(DB_PATH), timeout=5.0)
    conn.execute("PRAGMA journal_mode=WAL")
    return conn


def _init_db() -> None:
    with _db_lock, _db() as conn:
        # tabela legada (mantida por compat; nao e mais consultada, so escrita
        # no /pageview enquanto migramos, pra permitir rollback rapido).
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS sent (
                fbclid TEXT PRIMARY KEY,
                ts     INTEGER NOT NULL
            )
            """
        )
        conn.execute("CREATE INDEX IF NOT EXISTS idx_sent_ts ON sent(ts)")

        # nova tabela multi-evento: chave composta event_type + event_key.
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS events_sent (
                event_type TEXT NOT NULL,
                event_key  TEXT NOT NULL,
                ts         INTEGER NOT NULL,
                PRIMARY KEY (event_type, event_key)
            )
            """
        )
        conn.execute(
            "CREATE INDEX IF NOT EXISTS idx_events_sent_ts ON events_sent(ts)"
        )

        # limpeza defensiva > 30 dias em ambas
        cutoff = int(time.time()) - CLEANUP_TTL_SECONDS
        d1 = conn.execute("DELETE FROM sent WHERE ts < ?", (cutoff,)).rowcount
        d2 = conn.execute(
            "DELETE FROM events_sent WHERE ts < ?", (cutoff,)
        ).rowcount
        conn.commit()
        if d1 or d2:
            log.info(
                "dedupe cleanup: sent=%d events_sent=%d registros removidos",
                d1,
                d2,
            )


def _is_duplicate(event_type: str, event_key: str) -> bool:
    """True se (event_type, event_key) ja foi enviado dentro do TTL do tipo."""
    if not event_key:
        return False
    ttl = DEDUPE_TTL_BY_TYPE.get(event_type, DEDUPE_TTL_DEFAULT)
    cutoff = int(time.time()) - ttl
    with _db_lock, _db() as conn:
        row = conn.execute(
            "SELECT ts FROM events_sent "
            "WHERE event_type = ? AND event_key = ? AND ts >= ?",
            (event_type, event_key, cutoff),
        ).fetchone()
    return row is not None


def _mark_sent(event_type: str, event_key: str) -> None:
    if not event_key:
        return
    now = int(time.time())
    with _db_lock, _db() as conn:
        conn.execute(
            "INSERT OR REPLACE INTO events_sent "
            "(event_type, event_key, ts) VALUES (?, ?, ?)",
            (event_type, event_key, now),
        )
        # espelha no legado enquanto /pageview usa fbclid como chave
        if event_type == "pageview":
            conn.execute(
                "INSERT OR REPLACE INTO sent (fbclid, ts) VALUES (?, ?)",
                (event_key, now),
            )
        conn.commit()


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _client_ip_from_request() -> str:
    """Extrai IP real considerando proxies (X-Forwarded-For)."""
    xff = request.headers.get("X-Forwarded-For", "")
    if xff:
        return xff.split(",")[0].strip()
    return request.remote_addr or ""


def _build_fbc(fbclid: str, ts: int | None = None) -> str:
    """Formato Meta: fb.1.<ts_ms_or_s>.<fbclid>"""
    if not fbclid:
        return ""
    if ts is None:
        ts = int(time.time())
    return f"fb.1.{ts}.{fbclid}"


def _event_id(fbclid: str, ip: str, ua: str, ts_min: int) -> str:
    """Se tiver fbclid, ele e o event_id (garante dedupe com pixel browser).
    Senao, hash(ip+ua+minuto) pra permitir dedupe entre chamadas repetidas
    do mesmo user no mesmo minuto."""
    if fbclid:
        return fbclid
    raw = f"{ip}|{ua}|{ts_min}".encode("utf-8")
    return hashlib.sha256(raw).hexdigest()[:32]


def _collect_params() -> dict[str, str]:
    """Aceita GET (query string) ou POST (JSON ou form)."""
    src: dict[str, Any] = {}
    if request.is_json:
        body = request.get_json(silent=True) or {}
        if isinstance(body, dict):
            src.update(body)
    if request.form:
        for k in request.form:
            src.setdefault(k, request.form.get(k))
    for k in request.args:
        src.setdefault(k, request.args.get(k))

    out: dict[str, str] = {}
    for key in (
        "fbclid",
        "gclid",
        "utm_source",
        "utm_medium",
        "utm_campaign",
        "utm_content",
        "utm_term",
        "client_ip",
        "client_ua",
        "event_source_url",
    ):
        v = src.get(key)
        if v is None:
            continue
        out[key] = str(v).strip()[:2048]
    return out


# ---------------------------------------------------------------------------
# CAPI payload + envio
# ---------------------------------------------------------------------------
def _build_payload(params: dict[str, str], client_ip: str, client_ua: str) -> dict:
    now = int(time.time())
    fbclid = params.get("fbclid", "")
    event_source_url = params.get(
        "event_source_url", "https://www.px3lab.com.br/server-photorf/"
    )

    user_data: dict[str, Any] = {
        "client_ip_address": client_ip,
        "client_user_agent": client_ua,
    }
    if fbclid:
        user_data["fbc"] = _build_fbc(fbclid, now)

    custom_data: dict[str, Any] = {
        "content_name": "Server PhotoRF",
    }
    # UTMs vao em custom_data como referencia (nao entram em user_data)
    for k in ("utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"):
        v = params.get(k)
        if v:
            custom_data[k] = v

    ts_min = now // 60
    evt = {
        "event_name": "PageView",
        "event_time": now,
        "action_source": "website",
        "event_source_url": event_source_url,
        "event_id": _event_id(fbclid, client_ip, client_ua, ts_min),
        "user_data": user_data,
        "custom_data": custom_data,
    }

    payload: dict[str, Any] = {"data": [evt]}
    if META_TEST_EVENT_CODE:
        payload["test_event_code"] = META_TEST_EVENT_CODE
    return payload


def _send_meta(payload: dict) -> tuple[int, str]:
    try:
        r = requests.post(
            META_ENDPOINT,
            params={"access_token": META_TOKEN},
            json=payload,
            timeout=6,
        )
        return r.status_code, r.text
    except requests.RequestException as e:
        return -1, f"REQ_ERROR: {e}"


# ---------------------------------------------------------------------------
# Normalizacao + hashing (Meta CAPI - Purchase)
# ---------------------------------------------------------------------------
def _strip_accents(s: str) -> str:
    """Remove acentos: 'Sao Paulo' -> 'sao paulo'."""
    if not s:
        return ""
    nfkd = unicodedata.normalize("NFKD", s)
    return "".join(c for c in nfkd if not unicodedata.combining(c))


def _sha256(value: str) -> str:
    return hashlib.sha256(value.encode("utf-8")).hexdigest()


def _norm_email(v: str) -> str:
    return (v or "").strip().lower()


def _norm_phone(v: str) -> str:
    """So digitos (Meta espera E.164 sem '+', ex: 5511999999999)."""
    return re.sub(r"\D", "", v or "")


def _norm_name(v: str) -> str:
    """Lowercase + strip + remove acentos. Nao remove espaco (nome composto)."""
    return _strip_accents((v or "").strip().lower())


def _norm_city(v: str) -> str:
    """Lowercase + strip acentos + remove espacos e pontuacao."""
    base = _strip_accents((v or "").strip().lower())
    return re.sub(r"[^a-z0-9]", "", base)


def _norm_state(v: str) -> str:
    """Lowercase (Meta aceita sigla; 'rs' funciona)."""
    return _strip_accents((v or "").strip().lower())


def _norm_zip(v: str) -> str:
    """So digitos."""
    return re.sub(r"\D", "", v or "")


def _norm_country(v: str) -> str:
    """Lowercase ISO 3166-1 alpha-2 ('br')."""
    return (v or "").strip().lower()


def _hash_if(value: str) -> list[str]:
    """Retorna [sha256(value)] se value for truthy, senao lista vazia.
    Meta CAPI espera array pra em/ph/fn/ln/ct/st/zp/country."""
    return [_sha256(value)] if value else []


# ---------------------------------------------------------------------------
# CAPI payload - Purchase
# ---------------------------------------------------------------------------
def _build_purchase_payload(body: dict[str, Any]) -> tuple[dict, dict]:
    """Retorna (payload_meta, log_info). Nao envia."""
    now = int(time.time())

    email = _norm_email(str(body.get("email", "")))
    phone = _norm_phone(str(body.get("phone", "")))
    first_name = _norm_name(str(body.get("first_name", "")))
    last_name = _norm_name(str(body.get("last_name", "")))
    city = _norm_city(str(body.get("city", "")))
    state = _norm_state(str(body.get("state", "")))
    zip_code = _norm_zip(str(body.get("zip", "")))
    country = _norm_country(str(body.get("country", "BR")))

    fbclid = str(body.get("fbclid", "")).strip()
    client_ip = str(body.get("client_ip", "")).strip()
    client_ua = str(body.get("client_user_agent", "")).strip()

    event_id = str(body.get("event_id", "")).strip()
    if not event_id:
        # fallback defensivo - N8N deveria mandar sempre
        event_id = f"purchase-fallback-{now}-{_sha256(email)[:8]}"

    user_data: dict[str, Any] = {}
    if email:
        user_data["em"] = _hash_if(email)
    if phone:
        user_data["ph"] = _hash_if(phone)
    if first_name:
        user_data["fn"] = _hash_if(first_name)
    if last_name:
        user_data["ln"] = _hash_if(last_name)
    if city:
        user_data["ct"] = _hash_if(city)
    if state:
        user_data["st"] = _hash_if(state)
    if zip_code:
        user_data["zp"] = _hash_if(zip_code)
    if country:
        user_data["country"] = _hash_if(country)
    if client_ip:
        user_data["client_ip_address"] = client_ip
    if client_ua:
        user_data["client_user_agent"] = client_ua
    if fbclid:
        user_data["fbc"] = _build_fbc(fbclid, now)

    # custom_data
    try:
        value = float(body.get("value", 0) or 0)
    except (TypeError, ValueError):
        value = 0.0
    currency = str(body.get("currency", "BRL")).strip().upper() or "BRL"
    content_name = str(body.get("content_name", "")).strip()
    content_ids = body.get("content_ids") or []
    if isinstance(content_ids, str):
        content_ids = [content_ids]
    try:
        num_items = int(body.get("num_items", 0) or 0)
    except (TypeError, ValueError):
        num_items = 0

    custom_data: dict[str, Any] = {
        "currency": currency,
        "value": value,
        "content_type": "product",
        "content_category": "software_credit",
    }
    if content_name:
        custom_data["content_name"] = content_name
    if content_ids:
        custom_data["content_ids"] = list(content_ids)
    if num_items:
        custom_data["num_items"] = num_items

    evt = {
        "event_name": "Purchase",
        "event_time": now,
        "action_source": "system_generated",
        "event_source_url": "https://sistema.app.px3lab.com.br/",
        "event_id": event_id,
        "user_data": user_data,
        "custom_data": custom_data,
    }

    payload: dict[str, Any] = {"data": [evt]}
    # Prioridade: test_event_code do body (override pontual, ex: backfill)
    # senao usa o META_TEST_EVENT_CODE global do env.
    body_test_code = str(body.get("test_event_code", "")).strip()
    if body_test_code:
        payload["test_event_code"] = body_test_code
    elif META_TEST_EVENT_CODE:
        payload["test_event_code"] = META_TEST_EVENT_CODE

    log_info = {
        "event_id": event_id,
        "value": value,
        "currency": currency,
        "content_name": content_name,
        "num_items": num_items,
        "has_fbclid": bool(fbclid),
        "has_ip": bool(client_ip),
        "has_ua": bool(client_ua),
        "test_event_code": payload.get("test_event_code", ""),
    }
    return payload, log_info


# ---------------------------------------------------------------------------
# CAPI payload - Lead (waitlist / lista de espera)
# ---------------------------------------------------------------------------
def _build_lead_payload(body: dict[str, Any]) -> tuple[dict, dict]:
    """Constroi payload Meta Lead server-side.

    Espera:
      event_id (obrigatorio - MESMO do fbq browser p/ dedupe),
      email, phone, first_name (opcionais mas fortemente recomendados),
      client_ip, client_user_agent, event_source_url,
      fbclid, fbp (cookie _fbp), utms.
    """
    now = int(time.time())

    email = _norm_email(str(body.get("email", "")))
    phone = _norm_phone(str(body.get("phone", "")))
    first_name = _norm_name(str(body.get("first_name", "")))
    last_name = _norm_name(str(body.get("last_name", "")))
    country = _norm_country(str(body.get("country", "BR")))

    fbclid = str(body.get("fbclid", "")).strip()
    fbp = str(body.get("fbp", "")).strip()
    client_ip = str(body.get("client_ip", "")).strip()
    client_ua = str(body.get("client_user_agent", "")).strip()
    event_source_url = str(
        body.get("event_source_url", "https://espera.px3lab.com.br/")
    ).strip() or "https://espera.px3lab.com.br/"

    event_id = str(body.get("event_id", "")).strip()
    if not event_id:
        event_id = f"lead-fallback-{now}-{_sha256(email or phone)[:8]}"

    user_data: dict[str, Any] = {}
    if email:
        user_data["em"] = _hash_if(email)
    if phone:
        user_data["ph"] = _hash_if(phone)
    if first_name:
        user_data["fn"] = _hash_if(first_name)
    if last_name:
        user_data["ln"] = _hash_if(last_name)
    if country:
        user_data["country"] = _hash_if(country)
    if client_ip:
        user_data["client_ip_address"] = client_ip
    if client_ua:
        user_data["client_user_agent"] = client_ua
    if fbclid:
        user_data["fbc"] = _build_fbc(fbclid, now)
    if fbp:
        user_data["fbp"] = fbp

    custom_data: dict[str, Any] = {
        "content_name": str(body.get("content_name", "Waitlist Cloud PhotoRF 2.0")),
        "content_category": "waitlist",
    }
    # UTMs opcionais
    for k in ("utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"):
        v = body.get(k)
        if v:
            custom_data[k] = str(v)[:512]

    evt = {
        "event_name": "Lead",
        "event_time": now,
        "action_source": "website",
        "event_source_url": event_source_url,
        "event_id": event_id,
        "user_data": user_data,
        "custom_data": custom_data,
    }

    payload: dict[str, Any] = {"data": [evt]}
    body_test_code = str(body.get("test_event_code", "")).strip()
    if body_test_code:
        payload["test_event_code"] = body_test_code
    elif META_TEST_EVENT_CODE:
        payload["test_event_code"] = META_TEST_EVENT_CODE

    log_info = {
        "event_id": event_id,
        "has_email": bool(email),
        "has_phone": bool(phone),
        "has_fbclid": bool(fbclid),
        "has_fbp": bool(fbp),
        "has_ip": bool(client_ip),
        "has_ua": bool(client_ua),
        "event_source_url": event_source_url,
        "test_event_code": payload.get("test_event_code", ""),
    }
    return payload, log_info


# ---------------------------------------------------------------------------
# CAPI payload - Contact (clique botao WhatsApp)
# ---------------------------------------------------------------------------
def _build_contact_payload(body: dict[str, Any]) -> tuple[dict, dict]:
    """Constroi payload Meta Contact server-side (clique no botao WhatsApp).

    O payload vem do browser (JS da landing). Nao exige email/phone;
    o importante aqui e amarrar o clique ao usuario via fbp/fbc + IP/UA
    pra Meta atribuir conversao de volta pro anuncio.

    Espera:
      event_id (obrigatorio - MESMO do fbq browser p/ dedupe),
      event_source_url,
      user_data: { fbp, fbc, client_user_agent },
      custom_data: { content_name, utm_* }
    """
    now = int(time.time())

    # user_data pode vir aninhado (padrao Meta) ou flat (fallback).
    ud_in = body.get("user_data") or {}
    if not isinstance(ud_in, dict):
        ud_in = {}

    fbp = str(ud_in.get("fbp") or body.get("fbp") or "").strip()
    fbc = str(ud_in.get("fbc") or body.get("fbc") or "").strip()
    client_ua = str(
        ud_in.get("client_user_agent")
        or body.get("client_user_agent")
        or ""
    ).strip()
    client_ip = str(
        ud_in.get("client_ip_address")
        or body.get("client_ip")
        or ""
    ).strip()

    # fbclid solto: converte em fbc se nao veio pronto
    fbclid = str(body.get("fbclid", "")).strip()
    if not fbc and fbclid:
        fbc = _build_fbc(fbclid, now)

    event_source_url = str(
        body.get("event_source_url", "https://oferta.px3lab.com.br/")
    ).strip() or "https://oferta.px3lab.com.br/"

    event_id = str(body.get("event_id", "")).strip()
    if not event_id:
        event_id = f"contact-fallback-{now}-{_sha256(fbp or fbc or client_ua)[:8]}"

    user_data: dict[str, Any] = {}
    if client_ip:
        user_data["client_ip_address"] = client_ip
    if client_ua:
        user_data["client_user_agent"] = client_ua
    if fbp:
        user_data["fbp"] = fbp
    if fbc:
        user_data["fbc"] = fbc

    # custom_data: aceita payload aninhado (preferencial) OU flat (fallback)
    cd_in = body.get("custom_data") or {}
    if not isinstance(cd_in, dict):
        cd_in = {}

    custom_data: dict[str, Any] = {
        "content_name": str(
            cd_in.get("content_name")
            or body.get("content_name")
            or "whatsapp_oferta"
        )[:512],
    }
    # UTMs opcionais (preferir custom_data.*; fallback pra body.*)
    for k in ("utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"):
        v = cd_in.get(k) if cd_in.get(k) is not None else body.get(k)
        if v:
            custom_data[k] = str(v)[:512]

    # Campos extras arbitrarios dentro de custom_data (sem bloquear)
    for k, v in cd_in.items():
        if k in custom_data or v is None:
            continue
        if k in ("content_name",):
            continue
        try:
            custom_data[k] = str(v)[:512]
        except Exception:
            pass

    evt = {
        "event_name": "Contact",
        "event_time": now,
        "action_source": "website",
        "event_source_url": event_source_url,
        "event_id": event_id,
        "user_data": user_data,
        "custom_data": custom_data,
    }

    payload: dict[str, Any] = {"data": [evt]}
    body_test_code = str(body.get("test_event_code", "")).strip()
    if body_test_code:
        payload["test_event_code"] = body_test_code
    elif META_TEST_EVENT_CODE:
        payload["test_event_code"] = META_TEST_EVENT_CODE

    log_info = {
        "event_id": event_id,
        "has_fbp": bool(fbp),
        "has_fbc": bool(fbc),
        "has_ip": bool(client_ip),
        "has_ua": bool(client_ua),
        "content_name": custom_data.get("content_name", ""),
        "utm_campaign": custom_data.get("utm_campaign", ""),
        "event_source_url": event_source_url,
        "test_event_code": payload.get("test_event_code", ""),
    }
    return payload, log_info


# ---------------------------------------------------------------------------
# Flask app
# ---------------------------------------------------------------------------
app = Flask(__name__)


# ---------------------------------------------------------------------------
# CORS - permite POST do browser (LPs PX3)
# ---------------------------------------------------------------------------
# Origens aceitas (subdominios px3lab + agentesclimb.us).
_CORS_ALLOWED_SUFFIXES = (
    ".px3lab.com.br",
    ".agentesclimb.us",
)
_CORS_ALLOWED_EXACT = {
    "https://px3lab.com.br",
    "https://www.px3lab.com.br",
}


def _cors_origin_allowed(origin: str) -> bool:
    if not origin:
        return False
    if origin in _CORS_ALLOWED_EXACT:
        return True
    for suf in _CORS_ALLOWED_SUFFIXES:
        if origin.endswith(suf):
            # exige esquema https (bloqueia http://evil.px3lab.com.br.attacker.com)
            if origin.startswith("https://") or origin.startswith("http://"):
                return True
    return False


@app.after_request
def _cors_headers(resp):
    origin = request.headers.get("Origin", "")
    if _cors_origin_allowed(origin):
        resp.headers["Access-Control-Allow-Origin"] = origin
        resp.headers["Vary"] = "Origin"
        resp.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
        resp.headers["Access-Control-Allow-Headers"] = "Content-Type"
        resp.headers["Access-Control-Max-Age"] = "86400"
    return resp


@app.route("/contact", methods=["OPTIONS"])
@app.route("/lead", methods=["OPTIONS"])
@app.route("/pageview", methods=["OPTIONS"])
def _cors_preflight():
    # headers reais sao adicionados no after_request
    return ("", 204)


@app.get("/health")
def health():
    return jsonify(
        {
            "ok": True,
            "service": "capi_pageview_px3",
            "pixel": META_PIXEL_ID,
            "test_mode": bool(META_TEST_EVENT_CODE),
            "api_version": META_API_VERSION,
        }
    )


@app.route("/pageview", methods=["GET", "POST"])
def pageview():
    params = _collect_params()

    # IP/UA: preferir o que veio do dashboard-px3 (client_ip/client_ua),
    # senao usar o do proprio request (fallback).
    client_ip = params.get("client_ip") or _client_ip_from_request()
    client_ua = params.get("client_ua") or request.headers.get("User-Agent", "")

    fbclid = params.get("fbclid", "")

    # Dedupe
    if fbclid and _is_duplicate("pageview", fbclid):
        log.info(
            "SKIP dedupe pageview fbclid=%s utm_source=%s",
            fbclid[:32],
            params.get("utm_source", ""),
        )
        return jsonify({"ok": True, "dedupe": True})

    payload = _build_payload(params, client_ip, client_ua)
    status, body = _send_meta(payload)

    log_line = {
        "ts": datetime.now(timezone.utc).isoformat(),
        "status": status,
        "fbclid": fbclid[:32] if fbclid else "",
        "utm_source": params.get("utm_source", ""),
        "utm_campaign": params.get("utm_campaign", ""),
        "ip": client_ip,
        "test_mode": bool(META_TEST_EVENT_CODE),
        "resp": body[:400],
    }
    log.info("CAPI %s", json.dumps(log_line, ensure_ascii=False))

    if 200 <= status < 300:
        _mark_sent("pageview", fbclid)
        return jsonify({"ok": True, "meta_status": status})

    return jsonify({"ok": False, "meta_status": status, "resp": body[:400]}), 502


@app.post("/purchase")
def purchase():
    """Recebe compra real (N8N do PX3) e envia como Purchase server-side.

    Body JSON esperado (campos minimos: email OU phone, value, event_id):
      {
        "email": "...", "phone": "...",
        "first_name": "...", "last_name": "...",
        "city": "...", "state": "...", "zip": "...", "country": "BR",
        "value": 900.00, "currency": "BRL",
        "content_name": "CLOUD PHOTORF",
        "content_ids": ["CLOUD_PHOTORF"],
        "num_items": 10000,
        "event_id": "vra-9436",
        "fbclid": "(opcional)",
        "client_ip": "(opcional)",
        "client_user_agent": "(opcional)"
      }
    """
    body = request.get_json(silent=True) or {}
    if not isinstance(body, dict):
        return jsonify({"ok": False, "error": "invalid_json"}), 400

    event_id = str(body.get("event_id", "")).strip()
    email = str(body.get("email", "")).strip()
    phone = str(body.get("phone", "")).strip()
    if not email and not phone:
        return jsonify({"ok": False, "error": "email_or_phone_required"}), 400

    try:
        value = float(body.get("value", 0) or 0)
    except (TypeError, ValueError):
        value = 0.0
    if value <= 0:
        return jsonify({"ok": False, "error": "value_required"}), 400

    # Dedupe por event_id (janela 7 dias)
    if event_id and _is_duplicate("purchase", event_id):
        log.info("SKIP dedupe purchase event_id=%s", event_id)
        return jsonify({"ok": True, "dedupe": True, "event_id": event_id})

    payload, meta = _build_purchase_payload(body)
    status, resp_body = _send_meta(payload)

    log_line = {
        "ts": datetime.now(timezone.utc).isoformat(),
        "event": "Purchase",
        "status": status,
        "test_mode": bool(META_TEST_EVENT_CODE),
        **meta,
        "resp": resp_body[:400],
    }
    log.info("CAPI %s", json.dumps(log_line, ensure_ascii=False))

    if 200 <= status < 300:
        _mark_sent("purchase", meta["event_id"])
        return jsonify(
            {
                "ok": True,
                "meta_status": status,
                "event_id": meta["event_id"],
                "resp": resp_body[:400],
            }
        )

    return (
        jsonify(
            {
                "ok": False,
                "meta_status": status,
                "event_id": meta["event_id"],
                "resp": resp_body[:400],
            }
        ),
        502,
    )


@app.post("/lead")
def lead():
    """Recebe Lead da landing (server-side) e envia ao Meta CAPI.

    Body JSON esperado:
      {
        "event_id": "uuid gerado no browser (fbq eventID)",   # obrigatorio p/ dedupe
        "email":    "...",
        "phone":    "5511987654321",
        "first_name": "...", "last_name": "...",
        "client_ip": "1.2.3.4",           # opcional (senao pega do request)
        "client_user_agent": "...",       # opcional (senao pega do request)
        "event_source_url": "https://espera.px3lab.com.br/",
        "fbclid": "(opcional)",
        "fbp":    "(opcional, cookie _fbp)",
        "utm_source": ..., "utm_medium": ..., "utm_campaign": ...,
        "test_event_code": "(opcional, override)"
      }
    """
    body = request.get_json(silent=True) or {}
    if not isinstance(body, dict):
        return jsonify({"ok": False, "error": "invalid_json"}), 400

    email = str(body.get("email", "")).strip()
    phone = str(body.get("phone", "")).strip()
    if not email and not phone:
        return jsonify({"ok": False, "error": "email_or_phone_required"}), 400

    event_id = str(body.get("event_id", "")).strip()

    # Se o caller nao passou IP/UA, cai pro request atual
    if not body.get("client_ip"):
        body["client_ip"] = _client_ip_from_request()
    if not body.get("client_user_agent"):
        body["client_user_agent"] = request.headers.get("User-Agent", "")

    # Dedupe por event_id (janela 7 dias)
    if event_id and _is_duplicate("lead", event_id):
        log.info("SKIP dedupe lead event_id=%s", event_id)
        return jsonify({"ok": True, "dedupe": True, "event_id": event_id})

    payload, meta = _build_lead_payload(body)
    status, resp_body = _send_meta(payload)

    log_line = {
        "ts": datetime.now(timezone.utc).isoformat(),
        "event": "Lead",
        "status": status,
        "test_mode": bool(META_TEST_EVENT_CODE),
        **meta,
        "resp": resp_body[:400],
    }
    log.info("CAPI %s", json.dumps(log_line, ensure_ascii=False))

    if 200 <= status < 300:
        _mark_sent("lead", meta["event_id"])
        return jsonify(
            {
                "ok": True,
                "meta_status": status,
                "event_id": meta["event_id"],
                "resp": resp_body[:400],
            }
        )

    return (
        jsonify(
            {
                "ok": False,
                "meta_status": status,
                "event_id": meta["event_id"],
                "resp": resp_body[:400],
            }
        ),
        502,
    )


@app.post("/contact")
def contact():
    """Recebe clique no botao WhatsApp (browser) e envia Contact ao Meta CAPI.

    Chamado via fetch() do JS da landing. Dedupe por event_id (1h).

    Body JSON esperado:
      {
        "event_id": "uuid gerado no browser (fbq eventID)",   # obrigatorio p/ dedupe
        "event_name": "Contact",                              # ignorado, mantido p/ compat
        "event_source_url": "https://oferta.px3lab.com.br/...",
        "user_data": {
          "fbp": "fb.1.xxx.xxx",            # cookie _fbp
          "fbc": "fb.1.xxx.xxx",            # cookie _fbc (opcional, derivado do fbclid)
          "client_user_agent": "..."
        },
        "custom_data": {
          "content_name": "whatsapp_oferta_cloudphotorf2",
          "utm_source": "facebook", "utm_medium": "paid",
          "utm_campaign": "...", "utm_content": "...", "utm_term": "..."
        }
      }
    """
    body = request.get_json(silent=True) or {}
    if not isinstance(body, dict):
        return jsonify({"ok": False, "error": "invalid_json"}), 400

    event_id = str(body.get("event_id", "")).strip()

    # Se o caller nao passou IP/UA, cai pro request atual
    ud = body.get("user_data")
    if not isinstance(ud, dict):
        ud = {}
        body["user_data"] = ud
    if not ud.get("client_ip_address"):
        ud["client_ip_address"] = _client_ip_from_request()
    if not ud.get("client_user_agent"):
        ud["client_user_agent"] = request.headers.get("User-Agent", "")

    # Dedupe por event_id (janela 1h)
    if event_id and _is_duplicate("contact", event_id):
        log.info("SKIP dedupe contact event_id=%s", event_id)
        return jsonify({"ok": True, "deduped": True, "event_id": event_id})

    payload, meta = _build_contact_payload(body)
    status, resp_body = _send_meta(payload)

    log_line = {
        "ts": datetime.now(timezone.utc).isoformat(),
        "event": "Contact",
        "status": status,
        "test_mode": bool(META_TEST_EVENT_CODE),
        **meta,
        "resp": resp_body[:400],
    }
    log.info("CAPI %s", json.dumps(log_line, ensure_ascii=False))

    if 200 <= status < 300:
        _mark_sent("contact", meta["event_id"])
        return jsonify(
            {
                "ok": True,
                "event_id": meta["event_id"],
                "meta_status": status,
            }
        )

    return (
        jsonify(
            {
                "ok": False,
                "meta_status": status,
                "event_id": meta["event_id"],
                "resp": resp_body[:400],
            }
        ),
        500,
    )


# ---------------------------------------------------------------------------
# Boot
# ---------------------------------------------------------------------------
_init_db()

if __name__ == "__main__":
    port = int(os.environ.get("PORT", "8918"))
    host = os.environ.get("HOST", "0.0.0.0")
    log.info(
        "capi_px3 subindo: host=%s port=%s pixel=%s test_mode=%s routes=[/pageview,/purchase,/lead,/contact,/health]",
        host,
        port,
        META_PIXEL_ID,
        bool(META_TEST_EVENT_CODE),
    )
    app.run(host=host, port=port, debug=False, threaded=True)
