#!/usr/bin/env python3
"""
Modulo de VENDAS REAIS (CAPI PX3).

Parseia /opt/mia/logs/track_capi_px3.log procurando eventos Purchase enviados
ao Facebook. Retorna agregacoes por periodo, produto, vendedor e curva diaria.

- Live vs Backfill: eventos com event_id comecando por "backfill-vra-" sao
  considerados historicos (importacoes retroativas) e ficam SEPARADOS da
  contagem viva. O front escolhe se soma ou nao.
- Test mode: eventos com test_mode=true nao entram nas metricas de producao.
"""
from __future__ import annotations

import json
import logging
import re
import threading
import time
from datetime import datetime, timedelta, timezone
from pathlib import Path
from zoneinfo import ZoneInfo

CAPI_LOG = Path("/opt/mia/logs/track_capi_px3.log")
TZ_BR = ZoneInfo("America/Sao_Paulo")
TZ_UTC = ZoneInfo("UTC")

# Regex robusta pra achar o JSON de cada linha CAPI ...
_CAPI_LINE_RE = re.compile(r"CAPI\s+(\{.*\})\s*$")

# Cache in-memory (invalida quando o arquivo cresce)
_CACHE_LOCK = threading.Lock()
_CACHE: dict = {
    "purchases": [],           # lista de dict de todos os Purchase parseados
    "log_size": 0,
    "log_mtime": 0.0,
    "ts": 0.0,
}
_TTL = 120  # 2 min como piso mesmo que arquivo nao mude


log = logging.getLogger("vendas")


# ============================================================
# Parse
# ============================================================
def _parse_line(line: str) -> dict | None:
    line = line.strip()
    if not line or "Purchase" not in line:
        return None
    m = _CAPI_LINE_RE.search(line)
    if not m:
        return None
    try:
        d = json.loads(m.group(1))
    except Exception:
        return None
    if d.get("event") != "Purchase":
        return None
    return d


def _load_purchases() -> list[dict]:
    """Le o log inteiro e retorna todos os Purchase encontrados.
    Cache invalidado se o arquivo cresceu ou mtime mudou (piso de 2 min)."""
    with _CACHE_LOCK:
        try:
            st = CAPI_LOG.stat()
        except FileNotFoundError:
            _CACHE["purchases"] = []
            _CACHE["log_size"] = 0
            _CACHE["log_mtime"] = 0.0
            _CACHE["ts"] = time.time()
            return []

        now = time.time()
        cache_valido = (
            st.st_size == _CACHE["log_size"]
            and st.st_mtime == _CACHE["log_mtime"]
            and (now - _CACHE["ts"]) < _TTL
        )
        if cache_valido and _CACHE["purchases"]:
            return _CACHE["purchases"]

        purchases: list[dict] = []
        try:
            with CAPI_LOG.open("r", encoding="utf-8", errors="replace") as f:
                for line in f:
                    d = _parse_line(line)
                    if d:
                        purchases.append(d)
        except Exception as e:
            log.warning(f"Falha lendo CAPI log: {e}")
            return _CACHE.get("purchases") or []

        _CACHE["purchases"] = purchases
        _CACHE["log_size"] = st.st_size
        _CACHE["log_mtime"] = st.st_mtime
        _CACHE["ts"] = now
        return purchases


# ============================================================
# Helpers
# ============================================================
def _parse_ts(ts: str) -> datetime | None:
    if not ts:
        return None
    try:
        dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
        if dt.tzinfo is None:
            dt = dt.replace(tzinfo=TZ_UTC)
        return dt
    except Exception:
        return None


def _norm_produto(content_name: str) -> str:
    """Normaliza content_name variantes (FOTODRIVE/FOTO DRIVE/PHOTO DRIVE -> PHOTO DRIVE)."""
    n = (content_name or "").strip().upper()
    if not n:
        return "SEM PRODUTO"
    # Colapsa variantes de PHOTO DRIVE
    if n.replace(" ", "") in ("FOTODRIVE", "PHOTODRIVE"):
        return "PHOTO DRIVE"
    return n


def _extrair_vendedor(event_id: str) -> str:
    """
    event_id observado:
      backfill-vra-r39-20260527
      vra-paulo-prod-1787593107
      vra-9483
      vra-public-1787593154
      vra-integracao-test-1787593825

    Extrai a "tag" logo apos "vra-" (paulo, public, integracao, 9483 etc.)
    """
    eid = (event_id or "").strip().lower()
    if not eid:
        return "desconhecido"
    if eid.startswith("backfill-vra"):
        return "backfill"
    if eid.startswith("vra-"):
        parts = eid.split("-")
        if len(parts) >= 2:
            tag = parts[1]
            # Se for so digitos (ex: 9483), e uma execucao anonima do n8n
            if tag.isdigit():
                return f"execucao {tag}"
            return tag
    return "desconhecido"


def _is_backfill(event_id: str) -> bool:
    return (event_id or "").lower().startswith("backfill-vra")


def _is_test(purchase: dict) -> bool:
    return bool(purchase.get("test_mode"))


# ============================================================
# Agregacoes
# ============================================================
def filtrar_periodo(
    purchases: list[dict],
    inicio_utc: datetime,
    fim_utc: datetime,
    incluir_backfill: bool = False,
    incluir_test: bool = False,
) -> list[dict]:
    out = []
    for p in purchases:
        dt = _parse_ts(p.get("ts", ""))
        if not dt:
            continue
        if not (inicio_utc <= dt <= fim_utc):
            continue
        if not incluir_backfill and _is_backfill(p.get("event_id", "")):
            continue
        if not incluir_test and _is_test(p):
            continue
        out.append(p)
    return out


def _fmt_brl(value) -> str:
    try:
        v = float(value or 0)
    except (TypeError, ValueError):
        v = 0.0
    s = f"{v:,.2f}"
    s = s.replace(",", "@").replace(".", ",").replace("@", ".")
    return f"R$ {s}"


def agregar_vendas(vendas: list[dict]) -> dict:
    """Agrega KPIs, produtos, curva diaria, vendedores."""
    total = len(vendas)
    receita = 0.0
    por_produto: dict[str, dict] = {}
    por_dia: dict[str, dict] = {}   # YYYY-MM-DD (BR)
    por_vendedor: dict[str, dict] = {}

    for p in vendas:
        val = 0.0
        try:
            val = float(p.get("value") or 0)
        except (TypeError, ValueError):
            val = 0.0
        receita += val

        prod = _norm_produto(p.get("content_name", ""))
        if prod not in por_produto:
            por_produto[prod] = {"vendas": 0, "receita": 0.0}
        por_produto[prod]["vendas"] += 1
        por_produto[prod]["receita"] += val

        dt = _parse_ts(p.get("ts", ""))
        if dt:
            data_br = dt.astimezone(TZ_BR).strftime("%Y-%m-%d")
            if data_br not in por_dia:
                por_dia[data_br] = {"vendas": 0, "receita": 0.0}
            por_dia[data_br]["vendas"] += 1
            por_dia[data_br]["receita"] += val

        vend = _extrair_vendedor(p.get("event_id", ""))
        if vend not in por_vendedor:
            por_vendedor[vend] = {"vendas": 0, "receita": 0.0}
        por_vendedor[vend]["vendas"] += 1
        por_vendedor[vend]["receita"] += val

    ticket_medio = (receita / total) if total else 0.0

    # Ordenacoes
    produtos_lista = [
        {"produto": k, "vendas": v["vendas"], "receita": round(v["receita"], 2),
         "receita_fmt": _fmt_brl(v["receita"])}
        for k, v in por_produto.items()
    ]
    produtos_lista.sort(key=lambda x: -x["receita"])

    dias_lista = []
    for d in sorted(por_dia.keys()):
        info = por_dia[d]
        try:
            label = datetime.strptime(d, "%Y-%m-%d").strftime("%d/%m")
        except Exception:
            label = d
        dias_lista.append({
            "data": label,
            "data_iso": d,
            "vendas": info["vendas"],
            "receita": round(info["receita"], 2),
        })

    vendedores_lista = [
        {"vendedor": k, "vendas": v["vendas"], "receita": round(v["receita"], 2),
         "receita_fmt": _fmt_brl(v["receita"])}
        for k, v in por_vendedor.items()
    ]
    vendedores_lista.sort(key=lambda x: -x["receita"])

    return {
        "kpis": {
            "total_vendas": total,
            "receita_total": round(receita, 2),
            "receita_total_fmt": _fmt_brl(receita),
            "ticket_medio": round(ticket_medio, 2),
            "ticket_medio_fmt": _fmt_brl(ticket_medio),
        },
        "por_produto": produtos_lista,
        "curva_diaria": dias_lista,
        "vendedores": vendedores_lista,
    }


def comparar_hoje_media(purchases: list[dict], janela_dias: int = 7) -> dict:
    """Compara vendas de HOJE vs media dos ultimos `janela_dias` dias anteriores.
    Ignora backfill e test.
    """
    now_br = datetime.now(TZ_BR)
    hoje_ini = now_br.replace(hour=0, minute=0, second=0, microsecond=0)
    hoje_fim = now_br.replace(hour=23, minute=59, second=59, microsecond=0)

    janela_fim = hoje_ini - timedelta(seconds=1)
    janela_ini = janela_fim.replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=janela_dias - 1)

    hoje = filtrar_periodo(purchases, hoje_ini.astimezone(TZ_UTC), hoje_fim.astimezone(TZ_UTC))
    janela = filtrar_periodo(purchases, janela_ini.astimezone(TZ_UTC), janela_fim.astimezone(TZ_UTC))

    agg_hoje = agregar_vendas(hoje)
    agg_janela = agregar_vendas(janela)

    media_vendas = (agg_janela["kpis"]["total_vendas"] / janela_dias) if janela_dias else 0.0
    media_ticket = agg_janela["kpis"]["ticket_medio"]

    diff_vendas_pct = 0.0
    if media_vendas > 0:
        diff_vendas_pct = ((agg_hoje["kpis"]["total_vendas"] - media_vendas) / media_vendas * 100)

    diff_ticket_pct = 0.0
    if media_ticket > 0:
        diff_ticket_pct = ((agg_hoje["kpis"]["ticket_medio"] - media_ticket) / media_ticket * 100)

    return {
        "hoje": agg_hoje["kpis"],
        "media_janela": {
            "dias": janela_dias,
            "vendas_dia": round(media_vendas, 2),
            "ticket_medio": round(media_ticket, 2),
            "ticket_medio_fmt": _fmt_brl(media_ticket),
        },
        "diff": {
            "vendas_pct": round(diff_vendas_pct, 1),
            "ticket_pct": round(diff_ticket_pct, 1),
        },
    }


def coletar_vendas(
    inicio_utc: datetime,
    fim_utc: datetime,
    incluir_backfill: bool = False,
) -> dict:
    """Ponto de entrada usado pelo app.py.
    Retorna dict pronto com KPIs + curva + produtos + vendedores + alertas.
    """
    purchases = _load_purchases()
    live = filtrar_periodo(purchases, inicio_utc, fim_utc, incluir_backfill=incluir_backfill)
    agg = agregar_vendas(live)

    # Bloco historico (backfill) - referencia sempre visivel.
    # Inclui backfill test_mode (a maioria dos backfills atuais foram enviados em teste).
    historico_puro = [
        p for p in purchases
        if _is_backfill(p.get("event_id", "")) and _parse_ts(p.get("ts", "")) and inicio_utc <= _parse_ts(p.get("ts", "")) <= fim_utc
    ]
    agg_historico = agregar_vendas(historico_puro)

    alerta = comparar_hoje_media(purchases, janela_dias=7)

    return {
        "kpis": agg["kpis"],
        "por_produto": agg["por_produto"],
        "curva_diaria": agg["curva_diaria"],
        "vendedores": agg["vendedores"],
        "historico_backfill": {
            "total_vendas": agg_historico["kpis"]["total_vendas"],
            "receita_total": agg_historico["kpis"]["receita_total"],
            "receita_total_fmt": agg_historico["kpis"]["receita_total_fmt"],
        },
        "alerta": alerta,
        "total_no_log": len(purchases),
        "gerado_em": datetime.now(TZ_UTC).isoformat(timespec="seconds"),
    }


# ============================================================
# Atribuicao vendas -> canal (compras GHL x tag origem_*)
# ============================================================
# Consumido pelo modulo de atribuicao (usa dados ja carregados pelo tracking).
def cruzar_com_atribuicao(
    vendas_periodo: list[dict],
    atribuicao_por_canal: dict,
    gasto_meta: float,
    gasto_google: float,
) -> dict:
    """
    Combina o AGREGADO CAPI (vendas reais + receita) com o AGREGADO GHL
    (leads/compras por canal) pra calcular ROAS.

    - As vendas do CAPI PX3 sao a fonte de VERDADE de receita.
    - A proporcao de compras por canal vem do GHL (tags origem_*).
    - Atribuimos receita ao canal seguindo a mesma proporcao.
    """
    total_ghl_compras = sum(
        (dados.get("compras", 0) or 0) for dados in atribuicao_por_canal.values()
    )

    total_receita_capi = sum(float(p.get("value") or 0) for p in vendas_periodo)
    total_vendas_capi = len(vendas_periodo)

    canais_out = []
    for canal, dados in atribuicao_por_canal.items():
        compras_c = int(dados.get("compras", 0) or 0)
        leads_c = int(dados.get("leads", 0) or 0)

        if total_ghl_compras > 0:
            share = compras_c / total_ghl_compras
        else:
            share = 0.0

        receita_atribuida = round(total_receita_capi * share, 2)
        vendas_atribuidas = round(total_vendas_capi * share, 1)

        gasto = 0.0
        if canal == "meta":
            gasto = gasto_meta
        elif canal == "google":
            gasto = gasto_google

        roas = (receita_atribuida / gasto) if gasto > 0 else 0.0
        cpa = (gasto / compras_c) if compras_c > 0 else 0.0

        canais_out.append({
            "canal": canal,
            "leads": leads_c,
            "compras_ghl": compras_c,
            "share_compras": round(share * 100, 1),
            "vendas_atribuidas": vendas_atribuidas,
            "receita_atribuida": receita_atribuida,
            "gasto": round(gasto, 2),
            "roas": round(roas, 2),
            "cpa": round(cpa, 2),
        })

    # ordena por receita desc
    canais_out.sort(key=lambda x: -x["receita_atribuida"])

    return {
        "canais": canais_out,
        "totais": {
            "receita_capi": round(total_receita_capi, 2),
            "vendas_capi": total_vendas_capi,
            "compras_ghl": total_ghl_compras,
            "gasto_meta": round(gasto_meta, 2),
            "gasto_google": round(gasto_google, 2),
            "roas_meta": round((sum(c["receita_atribuida"] for c in canais_out if c["canal"] == "meta") / gasto_meta), 2) if gasto_meta > 0 else 0.0,
            "roas_google": round((sum(c["receita_atribuida"] for c in canais_out if c["canal"] == "google") / gasto_google), 2) if gasto_google > 0 else 0.0,
        },
    }
