import axios from "axios";
import { create } from "zustand";
import { pushVentasOffline, syncDelta, syncFull } from "../api/sync.service";
import db, {
  bulkPutItems,
  clearSyncedVentas,
  countPendingVentas,
  getPendingOfflineVentas,
  getSyncMeta,
  markVentaFailed,
  markVentaSynced,
  markVentasSyncing,
  putReferencial,
  revertVentasToPending,
  setSyncMeta,
} from "../offline/db";

const SYNC_INTERVAL_MS = 5 * 60 * 1000; // 5 minutos
const PING_INTERVAL_MS = 30 * 1000; // 30 segundos
const PING_TIMEOUT_MS = 5000; // 5 segundos timeout para el ping
const SYNC_META_KEY = "last_sync";

const API_BASE_URL =
  import.meta.env.VITE_API_BASE_URL || "http://localhost:3000/api/v1";

/**
 * Hace un ping real al backend para verificar conectividad.
 * No depende de navigator.onLine que solo detecta la interfaz de red.
 */
async function checkRealConnectivity() {
  try {
    const response = await axios.get(`${API_BASE_URL}/sync/ping`, {
      timeout: PING_TIMEOUT_MS,
    });
    return response.data?.ok === true;
  } catch {
    return false;
  }
}

export const useSyncStore = create((set, get) => ({
  // Estado
  isOnline: navigator.onLine,
  isSyncing: false,
  lastSyncAt: null,
  syncError: null,
  pendingVentasCount: 0,
  syncProgress: null, // { step: 'productos', current: 1, total: 4 }
  autoSyncInterval: null,
  pingInterval: null,
  _initialized: false,
  _onlineHandler: null,
  _offlineHandler: null,

  /**
   * Inicializar el store: cargar metadata, listeners de red, auto-sync.
   * Protegido contra múltiples llamadas (React StrictMode, re-mounts).
   */
  init: async () => {
    if (get()._initialized) return;
    set({ _initialized: true });

    // Solicitar persistent storage para proteger IndexedDB contra eviction
    if (navigator.storage?.persist) {
      try {
        const granted = await navigator.storage.persist();
        if (!granted) {
          console.warn(
            "[SyncStore] Persistent storage no concedido. Los datos podrían ser eliminados bajo presión de disco.",
          );
        }
      } catch (e) {
        console.warn("[SyncStore] Error solicitando persistent storage:", e);
      }
    }

    // Cargar última sync
    const meta = await getSyncMeta(SYNC_META_KEY);
    if (meta?.synced_at) {
      set({ lastSyncAt: meta.synced_at });
    }

    // Contar ventas pendientes
    const pending = await countPendingVentas();
    set({ pendingVentasCount: pending });

    // Check real de conectividad al iniciar
    const online = await checkRealConnectivity();
    set({ isOnline: online });

    // Limpiar listeners previos si existen
    const prevOnline = get()._onlineHandler;
    const prevOffline = get()._offlineHandler;
    if (prevOnline) window.removeEventListener("online", prevOnline);
    if (prevOffline) window.removeEventListener("offline", prevOffline);

    // Listeners de conexión del navegador como "hints"
    const handleOnline = async () => {
      const realOnline = await checkRealConnectivity();
      const wasOffline = !get().isOnline;
      set({ isOnline: realOnline });
      if (realOnline && wasOffline) {
        get().syncAuto();
      }
    };
    const handleOffline = () => {
      set({ isOnline: false });
    };

    window.addEventListener("online", handleOnline);
    window.addEventListener("offline", handleOffline);
    set({ _onlineHandler: handleOnline, _offlineHandler: handleOffline });

    // Ping periódico cada 30s para detectar cambios reales
    get().startPingCheck();

    // Iniciar auto-sync periódico
    get().startAutoSync();
  },

  /**
   * Sincronización completa (manual o primera vez).
   * Descarga TODO y reemplaza la DB local.
   */
  syncManual: async () => {
    const { isSyncing, isOnline } = get();
    if (isSyncing) return { success: false, reason: "already_syncing" };
    if (!isOnline) return { success: false, reason: "offline" };

    set({
      isSyncing: true,
      syncError: null,
      syncProgress: { step: "descargando", current: 0, total: 7 },
    });

    try {
      // 1. Primero push ventas pendientes
      await get()._pushPendingVentas();

      // 2. Descargar sync completo
      set({ syncProgress: { step: "productos", current: 1, total: 7 } });
      const data = await syncFull();

      // 3. Guardar en IndexedDB
      set({
        syncProgress: { step: "guardando productos", current: 2, total: 7 },
      });
      await bulkPutItems("productos", data.productos);

      set({
        syncProgress: { step: "guardando clientes", current: 3, total: 7 },
      });
      await bulkPutItems("clientes", data.clientes);

      set({
        syncProgress: { step: "guardando categorías", current: 4, total: 7 },
      });
      await bulkPutItems("categorias", data.categorias);

      set({
        syncProgress: {
          step: "guardando medios de pago",
          current: 5,
          total: 7,
        },
      });
      await bulkPutItems("medios_pago", data.medios_pago);

      set({
        syncProgress: { step: "guardando referenciales", current: 6, total: 7 },
      });
      await putReferencial("referenciales", data.referenciales);
      await bulkPutItems("numeraciones", data.numeraciones);
      await bulkPutItems("condiciones_pago", data.condiciones_pago);

      // 4. Guardar metadata
      set({ syncProgress: { step: "finalizando", current: 7, total: 7 } });
      const syncedAt = data.synced_at || new Date().toISOString();
      await setSyncMeta(SYNC_META_KEY, { synced_at: syncedAt });

      // Limpiar ventas ya sincronizadas
      await clearSyncedVentas();

      const pending = await countPendingVentas();

      set({
        isSyncing: false,
        lastSyncAt: syncedAt,
        syncError: null,
        syncProgress: null,
        pendingVentasCount: pending,
      });

      return { success: true, synced_at: syncedAt };
    } catch (error) {
      console.error("[SyncStore] Error en sync manual:", error);
      set({
        isSyncing: false,
        syncError: error.message || "Error de sincronización",
        syncProgress: null,
      });
      return { success: false, reason: error.message };
    }
  },

  /**
   * Sincronización automática (delta).
   * Solo descarga cambios desde la última sync.
   */
  syncAuto: async () => {
    const { isSyncing, isOnline, lastSyncAt } = get();
    if (isSyncing || !isOnline) return;

    // Si nunca se sincronizó, hacer sync completo
    if (!lastSyncAt) {
      return get().syncManual();
    }

    set({ isSyncing: true, syncError: null });

    try {
      // 1. Push ventas pendientes
      await get()._pushPendingVentas();

      // 2. Descargar delta
      const data = await syncDelta(lastSyncAt);

      // 3. Actualizar solo lo que cambió (bulkPut hace upsert)
      if (data.productos?.length > 0) {
        await bulkPutItems("productos", data.productos);
      }
      if (data.clientes?.length > 0) {
        await bulkPutItems("clientes", data.clientes);
      }
      if (data.categorias?.length > 0) {
        await bulkPutItems("categorias", data.categorias);
      }
      // Medios pago, numeraciones y condiciones: reemplazar siempre
      if (data.medios_pago) {
        await db.medios_pago.clear();
        await bulkPutItems("medios_pago", data.medios_pago);
      }
      if (data.numeraciones) {
        await db.numeraciones.clear();
        await bulkPutItems("numeraciones", data.numeraciones);
      }
      if (data.condiciones_pago) {
        await db.condiciones_pago.clear();
        await bulkPutItems("condiciones_pago", data.condiciones_pago);
      }

      // 4. Actualizar metadata
      const syncedAt = data.synced_at || new Date().toISOString();
      await setSyncMeta(SYNC_META_KEY, { synced_at: syncedAt });
      await clearSyncedVentas();
      const pending = await countPendingVentas();

      set({
        isSyncing: false,
        lastSyncAt: syncedAt,
        syncError: null,
        pendingVentasCount: pending,
      });
    } catch (error) {
      console.error("[SyncStore] Error en sync auto:", error);
      set({ isSyncing: false, syncError: error.message });
    }
  },

  /**
   * Push de ventas offline pendientes al servidor.
   * Usa status "syncing" como lock para evitar envíos duplicados.
   */
  _pushPendingVentas: async () => {
    try {
      const pending = await getPendingOfflineVentas();
      if (pending.length === 0) return;

      const localIds = pending.map((v) => v.localId);

      // LOCK: marcar como "syncing" ANTES de enviar para que otro sync no las tome
      await markVentasSyncing(localIds);

      const ventasPayload = pending.map((v) => ({
        ...v.data,
        _offline_id: v._offline_id,
        _offline_created_at: v.created_at,
      }));

      try {
        const result = await pushVentasOffline(ventasPayload);

        // Marcar como sincronizadas o fallidas según respuesta del backend
        for (const res of result.results || []) {
          const venta = pending.find((v) => v._offline_id === res._offline_id);
          if (!venta) continue;

          if (res.status === "queued" || res.status === "success") {
            await markVentaSynced(venta.localId);
          } else {
            await markVentaFailed(venta.localId, res.message);
          }
        }
      } catch (pushError) {
        // Si falla el POST (red caída), revertir a "pending" para reintentar
        console.error(
          "[SyncStore] Error en POST push/ventas, revertiendo:",
          pushError,
        );
        await revertVentasToPending(localIds);
      }

      const count = await countPendingVentas();
      set({ pendingVentasCount: count });
    } catch (error) {
      console.error("[SyncStore] Error pushing ventas offline:", error);
    }
  },

  /**
   * Iniciar auto-sync periódico.
   */
  startAutoSync: () => {
    const { autoSyncInterval } = get();
    if (autoSyncInterval) return; // ya está corriendo

    const interval = setInterval(() => {
      const { isOnline } = get();
      if (isOnline) {
        get().syncAuto();
      }
    }, SYNC_INTERVAL_MS);

    set({ autoSyncInterval: interval });
  },

  /**
   * Detener auto-sync.
   */
  stopAutoSync: () => {
    const { autoSyncInterval } = get();
    if (autoSyncInterval) {
      clearInterval(autoSyncInterval);
      set({ autoSyncInterval: null });
    }
  },

  /**
   * Iniciar ping periódico para verificar conectividad real.
   */
  startPingCheck: () => {
    const { pingInterval } = get();
    if (pingInterval) return;

    const interval = setInterval(async () => {
      const wasOnline = get().isOnline;
      const realOnline = await checkRealConnectivity();

      if (realOnline !== wasOnline) {
        set({ isOnline: realOnline });

        if (realOnline && !wasOnline) {
          console.log("[SyncStore] Conexión restaurada, sincronizando...");
          get().syncAuto();
        } else if (!realOnline && wasOnline) {
          console.log("[SyncStore] Conexión perdida");
        }
      }
    }, PING_INTERVAL_MS);

    set({ pingInterval: interval });
  },

  /**
   * Detener ping periódico.
   */
  stopPingCheck: () => {
    const { pingInterval } = get();
    if (pingInterval) {
      clearInterval(pingInterval);
      set({ pingInterval: null });
    }
  },

  /**
   * Actualizar conteo de ventas pendientes (llamar después de encolar).
   */
  refreshPendingCount: async () => {
    const count = await countPendingVentas();
    set({ pendingVentasCount: count });
  },
}));
