"""
sync_module.py — Backup remoto Six Hype (Sprint M).

Sincroniza estado do Zustand do app pro Supabase schema six_hype.*
pra usuário poder trocar device e ter audit log de mudanças.

Entidades sincronizadas (todas com owner_user_id = user.id):
  - operators
  - personagens
  - campanhas (com atos JSONB inline)
  - generated_packs (com pack/briefing/themes JSONB inline)

Stack: urllib stdlib (consistente com catalogo_module + chatwoot_sender).
Variáveis de env esperadas:
  UNI_DATA_SUPABASE_URL          → ex https://abc.supabase.co
  UNI_DATA_SUPABASE_SERVICE_ROLE → service role key (write)

Pra ligar/desligar sem mexer em código:
  SIX_HYPE_SYNC_ENABLED=true (default false — sync exige consenso do owner)

Idempotente: usa PATCH-or-upsert via PostgREST `Prefer: resolution=merge-duplicates`
(equivalente a INSERT ... ON CONFLICT DO UPDATE).
"""

from __future__ import annotations

import json
import os
import time
import urllib.error
import urllib.parse
import urllib.request
from typing import Any

SCHEMA = "six_hype"
SYNC_ENABLED = os.environ.get("SIX_HYPE_SYNC_ENABLED", "false").lower() in ("true", "1", "yes")
TIMEOUT_SEC = int(os.environ.get("SIX_HYPE_SYNC_TIMEOUT_SEC", "20"))

_CFG_CACHE = {"loaded_at": 0.0, "url": "", "key": ""}
_CFG_TTL_SEC = 60


class SyncError(Exception):
    pass


def _config() -> dict[str, str]:
    now = time.time()
    if now - _CFG_CACHE["loaded_at"] < _CFG_TTL_SEC and _CFG_CACHE["url"]:
        return _CFG_CACHE
    url = (
        os.environ.get("UNI_DATA_SUPABASE_URL")
        or os.environ.get("SUPABASE_URL")
        or ""
    ).rstrip("/")
    key = (
        os.environ.get("UNI_DATA_SUPABASE_SERVICE_ROLE")
        or os.environ.get("SUPABASE_SERVICE_ROLE")
        or ""
    )
    _CFG_CACHE.update({"loaded_at": now, "url": url, "key": key})
    return _CFG_CACHE


def is_enabled() -> bool:
    cfg = _config()
    return SYNC_ENABLED and bool(cfg["url"] and cfg["key"])


def status() -> dict[str, Any]:
    cfg = _config()
    return {
        "enabled": is_enabled(),
        "schema": SCHEMA,
        "config_present": bool(cfg["url"] and cfg["key"]),
        "env_flag": SYNC_ENABLED,
    }


def _rest_request(
    method: str,
    path: str,
    body: Any | None = None,
    prefer: str | None = None,
    query: dict[str, str] | None = None,
) -> tuple[int, Any]:
    cfg = _config()
    if not cfg["url"] or not cfg["key"]:
        raise SyncError("Credenciais Supabase ausentes")

    qs = ("?" + urllib.parse.urlencode(query, safe=",")) if query else ""
    url = f"{cfg['url']}/rest/v1/{path}{qs}"
    headers = {
        "apikey": cfg["key"],
        "Authorization": f"Bearer {cfg['key']}",
        "Content-Type": "application/json",
        "Accept": "application/json",
        "Accept-Profile": SCHEMA,
        "Content-Profile": SCHEMA,
    }
    if prefer:
        headers["Prefer"] = prefer

    data = json.dumps(body).encode("utf-8") if body is not None else None
    req = urllib.request.Request(url, data=data, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req, timeout=TIMEOUT_SEC) as resp:
            raw = resp.read().decode("utf-8") if resp.length != 0 else ""
            payload = json.loads(raw) if raw else None
            return resp.status, payload
    except urllib.error.HTTPError as e:
        msg = ""
        try:
            msg = e.read().decode("utf-8", errors="ignore")[:400]
        except Exception:
            pass
        raise SyncError(f"HTTP {e.code} {path}: {msg}") from e
    except urllib.error.URLError as e:
        raise SyncError(f"URLError {path}: {e.reason}") from e


def _upsert(table: str, rows: list[dict[str, Any]]) -> int:
    """Faz upsert idempotente via Prefer resolution=merge-duplicates."""
    if not rows:
        return 0
    code, _ = _rest_request(
        "POST",
        table,
        body=rows,
        prefer="resolution=merge-duplicates,return=minimal",
    )
    if code not in (200, 201, 204):
        raise SyncError(f"upsert {table} retornou {code}")
    return len(rows)


def _ensure_app_user(user: dict[str, Any]) -> str:
    """Garante registro em app_users e devolve o id."""
    payload = {
        "id": user["id"],
        "email": user["email"],
        "name": user.get("name"),
        "role": user.get("role"),
        "beta_access": bool(user.get("beta_access") or user.get("betaAccess")),
        "last_sync_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
    }
    _upsert("app_users", [payload])
    return user["id"]


# ─── Mapping JS camelCase → SQL snake_case ──────────────────────────────────

def _operator_to_row(op: dict[str, Any], owner_id: str) -> dict[str, Any]:
    return {
        "id": op["id"],
        "owner_user_id": owner_id,
        "name": op["name"],
        "role": op.get("role"),
        "email": op.get("email"),
        "avatar_seed": op.get("avatarSeed"),
        "is_owner": bool(op.get("isOwner")),
        "is_external": bool(op.get("isExternal")),
        "active": bool(op.get("active", True)),
    }


def _personagem_to_row(p: dict[str, Any], owner_id: str) -> dict[str, Any]:
    return {
        "id": p["id"],
        "owner_user_id": owner_id,
        "nome": p["nome"],
        "categoria": p["categoria"],
        "papel_narrativo_padrao": p["papelNarrativoPadrao"],
        "crm": p.get("crm"),
        "rqe": p.get("rqe"),
        "especialidade": p.get("especialidade"),
        "instagram_handle": p.get("instagramHandle"),
        "foto_url": p.get("fotoUrl"),
        "consent_imagem_until": p.get("consentImagemUntil") or None,
        "procedimentos_associados": p.get("procedimentosAssociados") or [],
        "tom_editorial": p.get("tomEditorial"),
        "restricoes": p.get("restricoes"),
        "ativo": bool(p.get("ativo", True)),
    }


def _campanha_to_row(c: dict[str, Any], owner_id: str) -> dict[str, Any]:
    return {
        "id": c["id"],
        "owner_user_id": owner_id,
        "nome": c["nome"],
        "big_idea": c.get("bigIdea"),
        "profile_id": c.get("profileId"),
        "product_id": c.get("productId"),
        "product_name": c.get("productName"),
        "personagem_principal_id": c.get("personagemPrincipalId"),
        "status": c.get("status", "rascunho"),
        "data_inicio": c.get("dataInicio") or None,
        "data_fim": c.get("dataFim") or None,
        "objective": c.get("objective"),
        "atos": c.get("atos") or [],
    }


def _pack_to_row(p: dict[str, Any], owner_id: str) -> dict[str, Any]:
    return {
        "id": p["id"],
        "owner_user_id": owner_id,
        "date": p["date"],
        "week": p.get("week"),
        "year": p.get("year"),
        "profile_id": p.get("profileId"),
        "profile_name": p.get("profileName"),
        "briefing_focus": p.get("briefingFocus"),
        "themes": p.get("themes") or [],
        "pack": p.get("pack") or [],
        "briefing": p.get("briefing") or None,
        "campanha_id": p.get("campanhaId"),
        "ato_id": p.get("atoId"),
        "etapa_funil": p.get("etapaFunil"),
    }


# ─── API pública ───────────────────────────────────────────────────────────

def push(user: dict[str, Any], payload: dict[str, Any]) -> dict[str, Any]:
    """
    Faz upsert das entidades vindas do app.

    user    = { id, email, name?, role?, betaAccess? }
    payload = { operators?, personagens?, campanhas?, generatedPacks? }

    Retorna {counts, duration_ms}.
    """
    if not is_enabled():
        raise SyncError("sync desabilitado (SIX_HYPE_SYNC_ENABLED=false)")

    started = time.time()
    owner_id = _ensure_app_user(user)
    counts: dict[str, int] = {}

    if isinstance(payload.get("operators"), list):
        counts["operators"] = _upsert(
            "operators",
            [_operator_to_row(o, owner_id) for o in payload["operators"]],
        )
    if isinstance(payload.get("personagens"), list):
        counts["personagens"] = _upsert(
            "personagens",
            [_personagem_to_row(p, owner_id) for p in payload["personagens"]],
        )
    if isinstance(payload.get("campanhas"), list):
        counts["campanhas"] = _upsert(
            "campanhas",
            [_campanha_to_row(c, owner_id) for c in payload["campanhas"]],
        )
    if isinstance(payload.get("generatedPacks"), list):
        counts["generated_packs"] = _upsert(
            "generated_packs",
            [_pack_to_row(p, owner_id) for p in payload["generatedPacks"]],
        )

    duration_ms = int((time.time() - started) * 1000)

    # Audit log (best-effort, não bloqueia)
    try:
        _upsert("sync_log", [{
            "user_id": owner_id,
            "direction": "push",
            "entities": list(counts.keys()),
            "counts": counts,
            "status": "ok",
            "duration_ms": duration_ms,
        }])
    except Exception:
        pass

    return {"counts": counts, "duration_ms": duration_ms}


def pull(user: dict[str, Any]) -> dict[str, Any]:
    """
    Lê do Supabase as entidades do owner pra restaurar em outro device.
    Retorna no formato Zustand-friendly (camelCase).
    """
    if not is_enabled():
        raise SyncError("sync desabilitado")

    started = time.time()
    owner_id = user["id"]
    _ensure_app_user(user)

    def _get(table: str) -> list[dict[str, Any]]:
        code, data = _rest_request(
            "GET",
            table,
            query={"owner_user_id": f"eq.{owner_id}", "select": "*"},
        )
        if code != 200:
            raise SyncError(f"pull {table} retornou {code}")
        return data or []

    operators_raw = _get("operators")
    personagens_raw = _get("personagens")
    campanhas_raw = _get("campanhas")
    packs_raw = _get("generated_packs")

    result = {
        "operators": [
            {
                "id": r["id"],
                "name": r["name"],
                "role": r.get("role"),
                "email": r.get("email"),
                "avatarSeed": r.get("avatar_seed"),
                "isOwner": r.get("is_owner"),
                "isExternal": r.get("is_external"),
                "active": r.get("active", True),
                "createdAt": r.get("created_at"),
            }
            for r in operators_raw
        ],
        "personagens": [
            {
                "id": r["id"],
                "nome": r["nome"],
                "categoria": r["categoria"],
                "papelNarrativoPadrao": r["papel_narrativo_padrao"],
                "crm": r.get("crm"),
                "rqe": r.get("rqe"),
                "especialidade": r.get("especialidade"),
                "instagramHandle": r.get("instagram_handle"),
                "fotoUrl": r.get("foto_url"),
                "consentImagemUntil": r.get("consent_imagem_until"),
                "procedimentosAssociados": r.get("procedimentos_associados") or [],
                "tomEditorial": r.get("tom_editorial"),
                "restricoes": r.get("restricoes"),
                "ativo": r.get("ativo", True),
                "createdAt": r.get("created_at"),
            }
            for r in personagens_raw
        ],
        "campanhas": [
            {
                "id": r["id"],
                "nome": r["nome"],
                "bigIdea": r.get("big_idea"),
                "profileId": r.get("profile_id"),
                "productId": r.get("product_id"),
                "productName": r.get("product_name"),
                "personagemPrincipalId": r.get("personagem_principal_id"),
                "status": r.get("status", "rascunho"),
                "dataInicio": r.get("data_inicio"),
                "dataFim": r.get("data_fim"),
                "objective": r.get("objective"),
                "atos": r.get("atos") or [],
                "createdAt": r.get("created_at"),
            }
            for r in campanhas_raw
        ],
        "generatedPacks": [
            {
                "id": r["id"],
                "date": r["date"],
                "week": r.get("week"),
                "year": r.get("year"),
                "profileId": r.get("profile_id"),
                "profileName": r.get("profile_name"),
                "briefingFocus": r.get("briefing_focus"),
                "themes": r.get("themes") or [],
                "pack": r.get("pack") or [],
                "briefing": r.get("briefing"),
                "campanhaId": r.get("campanha_id"),
                "atoId": r.get("ato_id"),
                "etapaFunil": r.get("etapa_funil"),
                "isGenerated": True,
            }
            for r in packs_raw
        ],
    }

    duration_ms = int((time.time() - started) * 1000)
    result["_meta"] = {
        "duration_ms": duration_ms,
        "counts": {k: len(v) for k, v in result.items() if isinstance(v, list)},
    }

    try:
        _upsert("sync_log", [{
            "user_id": owner_id,
            "direction": "pull",
            "entities": ["operators", "personagens", "campanhas", "generated_packs"],
            "counts": result["_meta"]["counts"],
            "status": "ok",
            "duration_ms": duration_ms,
        }])
    except Exception:
        pass

    return result


if __name__ == "__main__":
    print(json.dumps(status(), indent=2))
