"""
Sweet Angels SDR Webhook — porta 5055.

Recebe eventos do Linkia (GoHighLevel) quando um lead responde WhatsApp,
processa com o agente Angel (Claude) e devolve a resposta pelo Linkia.

Fluxo:
  1. Webhook chega → responde 200 imediatamente.
  2. Acumula mensagens do mesmo contato num buffer (debounce DEBOUNCE_SECONDS).
  3. Após o debounce, concatena tudo, chama Claude, aguarda TYPING_DELAY_SECONDS
     (simula digitando) e envia os blocos separados por [[BREAK]] com pausa entre eles.
"""

from __future__ import annotations

import json
import logging
import os
import sys
import threading
import time
from logging.handlers import RotatingFileHandler
from pathlib import Path
from typing import Any

from flask import Flask, jsonify, request

BASE_DIR = Path(__file__).resolve().parent
LOG_DIR = BASE_DIR / "logs"
LOG_DIR.mkdir(parents=True, exist_ok=True)

# ---------------------------------------------------------------------------
# Logging
# ---------------------------------------------------------------------------
log_handler = RotatingFileHandler(
    LOG_DIR / "webhook.log",
    maxBytes=5 * 1024 * 1024,
    backupCount=5,
    encoding="utf-8",
)
log_handler.setFormatter(
    logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
)
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(
    logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
)
logging.basicConfig(level=logging.INFO, handlers=[log_handler, stream_handler])
log = logging.getLogger("sdr_webhook")

# ---------------------------------------------------------------------------
# .env loading
# ---------------------------------------------------------------------------
try:
    from dotenv import load_dotenv  # type: ignore
    load_dotenv("/opt/mia/.env")
    load_dotenv(BASE_DIR.parent / "linkia.env")
except ImportError:
    for envfile in ("/opt/mia/.env", str(BASE_DIR.parent / "linkia.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())

# Imports proprios (depois do load_dotenv)
from angel_agent import gerar_resposta  # noqa: E402
from ghl_client import send_whatsapp_message  # noqa: E402

LINKIA_TOKEN = os.getenv("LINKIA_TOKEN", "").strip()
LINKIA_LOCATION_ID = os.getenv("LINKIA_LOCATION_ID", "").strip()

# Tempo de espera antes de processar (acumula msgs consecutivas do mesmo contato)
DEBOUNCE_SECONDS = float(os.getenv("SDR_DEBOUNCE_SECONDS", "5.0"))
# Pausa antes de enviar o primeiro bloco (simula "digitando...")
TYPING_DELAY_SECONDS = float(os.getenv("SDR_TYPING_DELAY_SECONDS", "3.0"))
# Pausa entre blocos consecutivos
BLOCK_DELAY_SECONDS = float(os.getenv("SDR_BLOCK_DELAY_SECONDS", "4.0"))

if not LINKIA_TOKEN:
    log.warning("LINKIA_TOKEN nao configurado — respostas nao serao enviadas ao GHL")

app = Flask(__name__)

# ---------------------------------------------------------------------------
# Debounce / acumulador por contato
# ---------------------------------------------------------------------------
_pending: dict[str, dict] = {}
_pending_lock = threading.Lock()


def _fire_contact(contact_id: str) -> None:
    """Chamado após o debounce. Concatena msgs, chama Claude e envia blocos."""
    with _pending_lock:
        data = _pending.pop(contact_id, None)
    if not data:
        return

    messages = data["messages"]
    conversation_id = data["conversation_id"]
    contact_meta = data["contact_meta"]
    mensagem_combinada = "\n".join(messages)

    log.info(
        "processando contact=%s msgs=%d combinado=%r",
        contact_id, len(messages), mensagem_combinada[:300],
    )

    try:
        resposta = gerar_resposta(
            contact_id=contact_id,
            mensagem_lead=mensagem_combinada,
            contact_meta=contact_meta,
        )
    except Exception as e:
        log.exception("erro gerando resposta: %s", e)
        return

    if not LINKIA_TOKEN:
        log.error("LINKIA_TOKEN ausente — nao enviando ao GHL")
        return

    # Divide em blocos (separador [[BREAK]]) e envia com delay entre eles
    blocos = [b.strip() for b in resposta.split("[[BREAK]]") if b.strip()]
    if not blocos:
        log.warning("resposta vazia apos split contact=%s", contact_id)
        return

    # Pausa que simula "digitando..." antes do primeiro bloco
    time.sleep(TYPING_DELAY_SECONDS)

    for i, bloco in enumerate(blocos):
        if i > 0:
            time.sleep(BLOCK_DELAY_SECONDS)
        ok, status, body = send_whatsapp_message(
            contact_id=contact_id,
            message=bloco,
            token=LINKIA_TOKEN,
            conversation_id=conversation_id,
        )
        log.info(
            "bloco %d/%d enviado ok=%s status=%s contact=%s preview=%r",
            i + 1, len(blocos), ok, status, contact_id, bloco[:80],
        )


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _get_first(d: dict[str, Any], *keys: str, default: Any = None) -> Any:
    for k in keys:
        if not k:
            continue
        if "." in k:
            cur: Any = d
            ok = True
            for part in k.split("."):
                if isinstance(cur, dict) and part in cur:
                    cur = cur[part]
                else:
                    ok = False
                    break
            if ok and cur not in (None, "", []):
                return cur
        elif k in d and d[k] not in (None, "", []):
            return d[k]
    return default


def extrair_payload(payload: dict[str, Any]) -> dict[str, Any]:
    contact_id = _get_first(
        payload,
        "contactId", "contact_id", "contact.id", "id",
    )
    conversation_id = _get_first(
        payload,
        "conversationId", "conversation_id", "conversation.id",
    )
    mensagem = _get_first(
        payload,
        "body", "message", "messageBody", "text",
        "message.body", "lastMessage.body",
    )
    first_name = _get_first(payload, "firstName", "first_name", "contact.firstName")
    last_name = _get_first(payload, "lastName", "last_name", "contact.lastName")
    phone = _get_first(payload, "phone", "contact.phone")
    direction = _get_first(payload, "direction", default="inbound")
    msg_type = _get_first(payload, "type", "messageType", default="WhatsApp")

    return {
        "contact_id": contact_id,
        "conversation_id": conversation_id,
        "mensagem": mensagem,
        "first_name": first_name,
        "last_name": last_name,
        "phone": phone,
        "direction": direction,
        "type": msg_type,
    }


# ---------------------------------------------------------------------------
# Rotas
# ---------------------------------------------------------------------------

@app.route("/health", methods=["GET"])
def health():
    return jsonify({
        "ok": True,
        "service": "sweet_angels_sdr",
        "port": 5055,
        "linkia_token_ok": bool(LINKIA_TOKEN),
        "debounce_s": DEBOUNCE_SECONDS,
    })


@app.route("/webhook/sweet-angels", methods=["POST"])
def webhook_sweet_angels():
    # 1. Parse payload
    try:
        payload = request.get_json(force=True, silent=False) or {}
    except Exception as e:
        log.warning("payload nao-JSON: %s | raw=%s", e, request.data[:400])
        return jsonify({"ok": False, "error": "invalid_json"}), 400

    log.info("webhook recebido: %s", json.dumps(payload, ensure_ascii=False)[:800])

    dados = extrair_payload(payload)

    # 2. Validacoes
    if not dados["contact_id"]:
        log.warning("webhook sem contact_id | payload=%s",
                    json.dumps(payload, ensure_ascii=False)[:400])
        return jsonify({"ok": False, "error": "missing_contact_id"}), 200

    if not dados["mensagem"]:
        log.info("webhook sem mensagem util — ignorando")
        return jsonify({"ok": True, "skipped": "no_message"}), 200

    if str(dados["direction"]).lower() == "outbound":
        log.info("mensagem outbound — ignorando (evita loop)")
        return jsonify({"ok": True, "skipped": "outbound"}), 200

    contact_id = str(dados["contact_id"])
    conversation_id = str(dados["conversation_id"]) if dados["conversation_id"] else None
    mensagem_lead = str(dados["mensagem"]).strip()
    contact_meta = {
        "first_name": dados.get("first_name"),
        "last_name": dados.get("last_name"),
        "phone": dados.get("phone"),
        "contact_id": contact_id,
        "conversation_id": conversation_id,
    }

    log.info(
        "recebido contact=%s nome=%s msg=%r",
        contact_id, dados.get("first_name"), mensagem_lead[:200],
    )

    # 3. Adiciona ao buffer de debounce (reseta o timer se ja existe)
    with _pending_lock:
        if contact_id in _pending:
            existing = _pending[contact_id]
            if existing.get("timer"):
                existing["timer"].cancel()
            existing["messages"].append(mensagem_lead)
            existing["conversation_id"] = conversation_id
            existing["contact_meta"] = contact_meta
            log.info("debounce reset contact=%s total_msgs=%d", contact_id, len(existing["messages"]))
        else:
            _pending[contact_id] = {
                "messages": [mensagem_lead],
                "timer": None,
                "conversation_id": conversation_id,
                "contact_meta": contact_meta,
            }

        timer = threading.Timer(DEBOUNCE_SECONDS, _fire_contact, args=(contact_id,))
        timer.daemon = True
        _pending[contact_id]["timer"] = timer
        timer.start()

    return jsonify({"ok": True, "queued": True, "debounce_s": DEBOUNCE_SECONDS}), 200


@app.route("/", methods=["GET"])
def index():
    return jsonify({
        "service": "Sweet Angels SDR Webhook",
        "endpoints": {
            "health": "GET /health",
            "webhook": "POST /webhook/sweet-angels",
        },
    })


# ---------------------------------------------------------------------------
# Entrypoint
# ---------------------------------------------------------------------------
if __name__ == "__main__":
    port = int(os.getenv("PORT", "5055"))
    host = os.getenv("HOST", "0.0.0.0")
    log.info("subindo Flask em %s:%s (debounce=%.1fs)", host, port, DEBOUNCE_SECONDS)
    app.run(host=host, port=port, debug=False, threaded=True)
