#!/usr/bin/env python3
"""
Modulo "Leads de Trafego - Setembro/2026".

Filtra contatos GHL de setembro/2026 que vieram de trafego pago
(Meta/Google/paid/ads via utm_source, source nativo, attributions ou tags).

Expoe duas funcoes publicas:
  - filtrar_leads(contatos) -> list[dict]  (leads normalizados prontos pra UI)
  - buscar_observacao(contact_id, headers) -> dict  (ultima nota [VENDEDORA...])
  - salvar_observacao(contact_id, texto, user, headers) -> dict  (cria nota nova)
"""
from __future__ import annotations

from datetime import datetime
from zoneinfo import ZoneInfo
from typing import Iterable

import requests

try:
    # import absoluto quando rodando dentro do dashboard
    from config_ghl import (
        UTM_FIELD_SOURCE,
        UTM_FIELD_CAMPAIGN,
        UTM_FIELD_MEDIUM,
    )
except Exception:
    # fallback: ids hardcoded se o import quebrar
    UTM_FIELD_SOURCE = "ysHWPzyR0uZ2nd8D9sbi"
    UTM_FIELD_CAMPAIGN = "H16g1x7tRA8OoHnqiTdp"
    UTM_FIELD_MEDIUM = "Hiabk9sp0zIkgLjUYJjR"


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

# Janela "setembro/2026" fixa (criterio do briefing)
JANELA_INICIO_UTC = datetime(2026, 9, 1, 0, 0, 0, tzinfo=TZ_BR).astimezone(TZ_UTC)
JANELA_FIM_UTC = datetime(2026, 9, 30, 23, 59, 59, tzinfo=TZ_BR).astimezone(TZ_UTC)

# Tokens de "veio do trafego" (OR entre as regras)
TOKENS_UTM_OU_SOURCE = ("facebook", "instagram", "ig", "fb", "meta", "paid", "ads")
TOKENS_TAG = ("trafego", "meta-ads", "facebook-ads", "instagram-ads", "pago")

# Prefixo das notas de vendedora (pra separar das demais)
NOTA_PREFIX_VENDEDORA = "[VENDEDORA"

GHL_BASE = "https://services.leadconnectorhq.com"


# ============================================================
# Helpers
# ============================================================
def _parse_dt(v: str):
    if not v:
        return None
    try:
        return datetime.fromisoformat(v.replace("Z", "+00:00"))
    except Exception:
        return None


def _lower(v) -> str:
    return (v or "").lower() if isinstance(v, str) else ""


def _custom_fields_map(c: dict) -> dict:
    out = {}
    for cf in c.get("customFields") or []:
        cid = cf.get("id")
        if cid:
            out[cid] = cf.get("value")
    return out


def _sanitize_phone(raw) -> str:
    """So digitos; prefixa 55 se nao tiver codigo pais e tiver 10-11 digitos."""
    if not raw:
        return ""
    digits = "".join(ch for ch in str(raw) if ch.isdigit())
    if not digits:
        return ""
    if len(digits) in (10, 11):
        digits = "55" + digits
    return digits


def _format_phone_br(digits: str) -> str:
    """Formata para exibicao: +55 (11) 99999-9999 se possivel."""
    if not digits:
        return ""
    d = digits
    if d.startswith("55") and len(d) in (12, 13):
        pais, ddd, resto = d[:2], d[2:4], d[4:]
        if len(resto) == 9:
            return f"+{pais} ({ddd}) {resto[:5]}-{resto[5:]}"
        if len(resto) == 8:
            return f"+{pais} ({ddd}) {resto[:4]}-{resto[4:]}"
    return "+" + d


def _match_tokens(valor: str, tokens: Iterable[str]) -> bool:
    v = _lower(valor)
    if not v:
        return False
    return any(tok in v for tok in tokens)


def _attribution_match(attributions: list, tokens: Iterable[str]) -> bool:
    """Attribution nativo GHL (array `attributions`) com source batendo tokens."""
    if not attributions or not isinstance(attributions, list):
        return False
    for att in attributions:
        if not isinstance(att, dict):
            continue
        s = _lower(att.get("utmSource") or att.get("sessionSource") or "")
        m = _lower(att.get("utmMedium") or att.get("medium") or "")
        if any(tok in s for tok in tokens) or any(tok in m for tok in tokens):
            return True
    return False


def _tags_match(tags: list, tokens: Iterable[str]) -> bool:
    if not tags:
        return False
    tags_low = [_lower(t) for t in tags]
    for tag in tags_low:
        if any(tok in tag for tok in tokens):
            return True
    return False


# ============================================================
# Filtragem principal
# ============================================================
def filtrar_leads(contatos: list[dict]) -> dict:
    """
    Filtra contatos de setembro/2026 que vieram do trafego.

    Retorna:
      {
        "leads":  [ { ...normalizado... } ],
        "breakdown": { "utm_source": N, "source": N, "attribution": N, "tags": N },
        "janela": { "inicio_utc": iso, "fim_utc": iso }
      }

    Dedupe por contactId. Lead que bate em >1 regra conta nas N regras no
    breakdown, mas aparece 1x na lista.
    """
    breakdown = {"utm_source": 0, "source": 0, "attribution": 0, "tags": 0}
    seen: set[str] = set()
    out: list[dict] = []

    for c in contatos or []:
        cid = c.get("id")
        if not cid or cid in seen:
            continue

        dt = _parse_dt(c.get("dateAdded"))
        if not dt or not (JANELA_INICIO_UTC <= dt <= JANELA_FIM_UTC):
            continue

        cfs = _custom_fields_map(c)
        utm_source_val = cfs.get(UTM_FIELD_SOURCE)
        utm_campaign_val = cfs.get(UTM_FIELD_CAMPAIGN)
        utm_medium_val = cfs.get(UTM_FIELD_MEDIUM)

        source_val = c.get("source")
        tags = c.get("tags") or []
        attributions = c.get("attributions") or []

        reasons: list[str] = []
        if _match_tokens(utm_source_val, TOKENS_UTM_OU_SOURCE):
            reasons.append("utm_source")
        if _match_tokens(source_val, TOKENS_UTM_OU_SOURCE):
            reasons.append("source")
        if _attribution_match(attributions, TOKENS_UTM_OU_SOURCE):
            reasons.append("attribution")
        if _tags_match(tags, TOKENS_TAG):
            reasons.append("tags")

        if not reasons:
            continue

        for r in reasons:
            breakdown[r] += 1

        seen.add(cid)

        # Nome: firstName + lastName (fallback contactName / email / id)
        first = (c.get("firstName") or "").strip()
        last = (c.get("lastName") or "").strip()
        nome = (f"{first} {last}").strip()
        if not nome:
            nome = c.get("contactName") or c.get("email") or f"Lead {cid[:6]}"

        # Telefone: phone principal ou primeiro additional
        phone_raw = c.get("phone")
        if not phone_raw:
            addl = c.get("additionalPhones") or []
            if addl and isinstance(addl, list):
                primeiro = addl[0]
                if isinstance(primeiro, dict):
                    phone_raw = primeiro.get("phone") or primeiro.get("value")
                else:
                    phone_raw = str(primeiro)

        phone_digits = _sanitize_phone(phone_raw)

        out.append({
            "contact_id": cid,
            "nome": nome,
            "phone_raw": phone_raw or "",
            "phone_digits": phone_digits,
            "phone_display": _format_phone_br(phone_digits) if phone_digits else "",
            "wa_url": f"https://wa.me/{phone_digits}" if phone_digits else "",
            "email": c.get("email") or "",
            "date_added": c.get("dateAdded") or "",
            "date_added_br": _fmt_data_br(dt),
            "utm_source": utm_source_val or "",
            "utm_campaign": utm_campaign_val or "",
            "utm_medium": utm_medium_val or "",
            "source": source_val or "",
            "tags": tags,
            "reasons": reasons,
            "linkia_url": (
                f"https://app.linkia.app/v2/location/"
                f"{_get_location_id()}/contacts/detail/{cid}"
            ),
        })

    # Ordena: mais recente primeiro
    out.sort(key=lambda r: r.get("date_added") or "", reverse=True)

    return {
        "leads": out,
        "breakdown": breakdown,
        "janela": {
            "inicio_utc": JANELA_INICIO_UTC.isoformat(),
            "fim_utc": JANELA_FIM_UTC.isoformat(),
            "label": "Setembro/2026",
        },
    }


def _fmt_data_br(dt_utc) -> str:
    if not dt_utc:
        return ""
    try:
        dt_br = dt_utc.astimezone(TZ_BR)
        return dt_br.strftime("%d/%m %H:%M")
    except Exception:
        return ""


def _get_location_id() -> str:
    try:
        from config_ghl import PX3_LOCATION_ID
        return PX3_LOCATION_ID
    except Exception:
        return "W7PGxpfbsFaEEUoQOtUb"


# ============================================================
# Notas no GHL (observacao da vendedora)
# ============================================================
def buscar_observacao(contact_id: str, headers: dict, timeout: int = 20) -> dict:
    """
    GET /contacts/{id}/notes -> retorna a nota mais recente que comeca com
    NOTA_PREFIX_VENDEDORA (pra mostrar como rascunho na textarea).

    Retorna:
      {"ok": True, "nota": {"id": "...", "body": "...", "createdAt": "...",
                            "texto_puro": "..."}}
      ou {"ok": True, "nota": None} se nao houver nota de vendedora.
      ou {"ok": False, "erro": "..."}
    """
    url = f"{GHL_BASE}/contacts/{contact_id}/notes"
    try:
        r = requests.get(url, headers=headers, timeout=timeout)
        # IMPORTANTE: sempre consumir o body antes de qualquer coisa (gotcha GHL)
        try:
            data = r.json()
        except Exception:
            data = {"raw": (r.text or "")[:500]}
        if r.status_code != 200:
            return {"ok": False, "erro": f"GHL {r.status_code}", "detalhe": data}
    except Exception as e:
        return {"ok": False, "erro": f"request_fail: {e}"}

    notes = data.get("notes") or []
    # Filtra pelas que sao de vendedora, ordena por createdAt desc
    vendedora_notes = [
        n for n in notes if (n.get("body") or "").startswith(NOTA_PREFIX_VENDEDORA)
    ]
    if not vendedora_notes:
        return {"ok": True, "nota": None}
    vendedora_notes.sort(key=lambda n: n.get("createdAt") or "", reverse=True)
    nota = vendedora_notes[0]
    body = nota.get("body") or ""
    # Extrai so o texto puro (depois do prefixo `] `)
    texto_puro = body
    fechamento = body.find("] ")
    if fechamento != -1:
        texto_puro = body[fechamento + 2:].strip()
    return {
        "ok": True,
        "nota": {
            "id": nota.get("id"),
            "body": body,
            "texto_puro": texto_puro,
            "createdAt": nota.get("createdAt"),
            "userId": nota.get("userId"),
        },
    }


def salvar_observacao(contact_id: str, texto: str, vendedora: str, headers: dict,
                      timeout: int = 20) -> dict:
    """
    Cria uma NOTA NOVA no contato (nao sobrescreve a anterior).

    Prefixo: [VENDEDORA nome 07/10/2026 14h32]
    """
    texto = (texto or "").strip()
    if not texto:
        return {"ok": False, "erro": "texto_vazio"}
    if len(texto) > 4000:
        texto = texto[:4000]

    agora_br = datetime.now(TZ_BR)
    carimbo = agora_br.strftime("%d/%m/%Y %Hh%M")
    nome_vendedora = (vendedora or "vendedora").strip() or "vendedora"
    body = f"{NOTA_PREFIX_VENDEDORA} {nome_vendedora} {carimbo}] {texto}"

    url = f"{GHL_BASE}/contacts/{contact_id}/notes"
    payload = {"body": body}

    try:
        r = requests.post(url, headers=headers, json=payload, timeout=timeout)
        # SEMPRE consumir body (gotcha GHL)
        try:
            data = r.json()
        except Exception:
            data = {"raw": (r.text or "")[:500]}
        if r.status_code not in (200, 201):
            return {
                "ok": False,
                "erro": f"GHL {r.status_code}",
                "detalhe": data,
            }
    except Exception as e:
        return {"ok": False, "erro": f"request_fail: {e}"}

    nota = (data or {}).get("note") or (data or {})
    return {
        "ok": True,
        "nota": {
            "id": nota.get("id"),
            "body": nota.get("body") or body,
            "createdAt": nota.get("createdAt"),
            "texto_puro": texto,
        },
    }
