-- =============================================================================
-- ERP TRANSPORTISTAS - Schema Offline DDL
-- =============================================================================
-- Archivo: 10-offline-schema-ddl.sql
-- Version: 1.0.0
-- Fecha: 2026-01-28
-- Descripcion: Cola de operaciones offline para sincronizacion
-- Basado en: OfflineQueue.entity.ts (TASK-007)
-- =============================================================================

-- =============================================================================
-- SCHEMA OFFLINE
-- =============================================================================

CREATE SCHEMA IF NOT EXISTS offline;
COMMENT ON SCHEMA offline IS 'Sistema de sincronizacion offline para operaciones en campo';

-- =============================================================================
-- TIPOS ENUMERADOS
-- =============================================================================

-- Tipos de operacion que pueden ser encoladas offline
CREATE TYPE offline.tipo_operacion_offline AS ENUM (
  -- GPS Operations
  'GPS_POSICION',
  'GPS_EVENTO',

  -- Dispatch Operations
  'VIAJE_ESTADO',
  'VIAJE_EVENTO',
  'CHECKIN',
  'CHECKOUT',

  -- POD Operations
  'POD_FOTO',
  'POD_FIRMA',
  'POD_DOCUMENTO',

  -- Checklist Operations
  'CHECKLIST_ITEM',
  'CHECKLIST_COMPLETADO',

  -- Generic
  'CUSTOM'
);

COMMENT ON TYPE offline.tipo_operacion_offline IS 'Tipos de operaciones que pueden encolarse offline';

-- Estados de sincronizacion para operaciones encoladas
CREATE TYPE offline.estado_sincronizacion AS ENUM (
  'PENDIENTE',    -- Esperando sincronizacion
  'EN_PROCESO',   -- Sincronizacion en progreso
  'COMPLETADO',   -- Sincronizado exitosamente
  'ERROR',        -- Error durante sincronizacion
  'CONFLICTO',    -- Conflicto de datos detectado
  'DESCARTADO'    -- Operacion descartada
);

COMMENT ON TYPE offline.estado_sincronizacion IS 'Estados del proceso de sincronizacion';

-- Niveles de prioridad para cola de sincronizacion
CREATE TYPE offline.prioridad_sync AS ENUM (
  '1',  -- CRITICA: Posiciones GPS, eventos de seguridad
  '2',  -- ALTA: POD, cambios de estado
  '3',  -- NORMAL: Items de checklist, notas
  '4'   -- BAJA: Fotos, documentos
);

COMMENT ON TYPE offline.prioridad_sync IS 'Prioridades de sincronizacion (1=critica, 4=baja)';

-- =============================================================================
-- TABLA: offline_queue
-- =============================================================================

CREATE TABLE offline.offline_queue (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES public.tenants(id),

  -- Referencias opcionales
  dispositivo_id UUID,                -- Dispositivo que genero la operacion
  usuario_id UUID,                    -- Usuario que genero la operacion
  unidad_id UUID,                     -- Unidad relacionada
  viaje_id UUID,                      -- Viaje relacionado

  -- Detalles de la operacion
  tipo_operacion offline.tipo_operacion_offline NOT NULL,
  estado offline.estado_sincronizacion DEFAULT 'PENDIENTE',
  prioridad offline.prioridad_sync DEFAULT '3',

  -- Payload - datos a sincronizar (JSONB)
  payload JSONB NOT NULL,

  -- Metadata de la operacion
  endpoint_destino VARCHAR(255) NOT NULL,      -- URL/endpoint destino
  metodo_http VARCHAR(10) DEFAULT 'POST',      -- GET, POST, PUT, PATCH, DELETE

  -- Timestamps offline
  creado_offline_en TIMESTAMPTZ NOT NULL,      -- Timestamp cuando se creo offline
  cliente_id VARCHAR(100),                     -- UUID generado en cliente para deduplicacion

  -- Tracking de sincronizacion
  intentos_sync INT DEFAULT 0,                 -- Contador de intentos
  max_intentos INT DEFAULT 5,                  -- Maximo de intentos permitidos
  ultimo_intento_en TIMESTAMPTZ,               -- Timestamp del ultimo intento
  sincronizado_en TIMESTAMPTZ,                 -- Timestamp cuando se sincronizo exitosamente

  -- Manejo de errores
  ultimo_error TEXT,                           -- Ultimo mensaje de error
  historial_errores JSONB DEFAULT '[]',        -- Array de {timestamp, error}

  -- Resolucion de conflictos
  version_servidor INT,                        -- Version del servidor para conflictos
  resolucion_conflicto VARCHAR(50),            -- 'CLIENT_WINS', 'SERVER_WINS', 'MERGE', 'MANUAL'

  -- Optimizacion de ancho de banda
  tamano_bytes INT,                            -- Tamano del payload en bytes
  comprimido BOOLEAN DEFAULT FALSE,            -- Si el payload esta comprimido

  -- Auditoria
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- =============================================================================
-- INDICES
-- =============================================================================

-- Indice principal por tenant
CREATE INDEX idx_offline_queue_tenant
  ON offline.offline_queue(tenant_id);

-- Indice por dispositivo para sincronizacion
CREATE INDEX idx_offline_queue_dispositivo
  ON offline.offline_queue(dispositivo_id)
  WHERE dispositivo_id IS NOT NULL;

-- Indice por estado para procesamiento de cola
CREATE INDEX idx_offline_queue_estado
  ON offline.offline_queue(estado);

-- Indice por prioridad y timestamp para ordenamiento de cola FIFO con prioridad
CREATE INDEX idx_offline_queue_prioridad
  ON offline.offline_queue(prioridad, creado_offline_en);

-- Indice compuesto para operaciones pendientes por tenant
CREATE INDEX idx_offline_queue_pendientes
  ON offline.offline_queue(tenant_id, estado, prioridad)
  WHERE estado IN ('PENDIENTE', 'EN_PROCESO', 'ERROR');

-- Indice para buscar por cliente_id (deduplicacion)
CREATE UNIQUE INDEX idx_offline_queue_cliente_id
  ON offline.offline_queue(tenant_id, cliente_id)
  WHERE cliente_id IS NOT NULL;

-- Indice por viaje para consultas relacionadas
CREATE INDEX idx_offline_queue_viaje
  ON offline.offline_queue(viaje_id)
  WHERE viaje_id IS NOT NULL;

-- Indice por unidad para consultas relacionadas
CREATE INDEX idx_offline_queue_unidad
  ON offline.offline_queue(unidad_id)
  WHERE unidad_id IS NOT NULL;

-- Indice para limpieza de registros antiguos sincronizados
CREATE INDEX idx_offline_queue_sincronizado
  ON offline.offline_queue(sincronizado_en)
  WHERE estado = 'COMPLETADO';

-- =============================================================================
-- TRIGGER: Actualizar updated_at automaticamente
-- =============================================================================

CREATE OR REPLACE FUNCTION offline.update_offline_queue_timestamp()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = NOW();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_offline_queue_updated_at
  BEFORE UPDATE ON offline.offline_queue
  FOR EACH ROW
  EXECUTE FUNCTION offline.update_offline_queue_timestamp();

-- =============================================================================
-- RLS POLICIES
-- =============================================================================

ALTER TABLE offline.offline_queue ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation_offline_queue ON offline.offline_queue
  USING (tenant_id = current_setting('app.tenant_id')::uuid);

-- =============================================================================
-- COMENTARIOS
-- =============================================================================

COMMENT ON TABLE offline.offline_queue IS 'Cola de operaciones pendientes de sincronizacion desde dispositivos offline';

COMMENT ON COLUMN offline.offline_queue.tipo_operacion IS 'Tipo de operacion: GPS, POD, Checklist, etc.';
COMMENT ON COLUMN offline.offline_queue.estado IS 'Estado actual de la sincronizacion';
COMMENT ON COLUMN offline.offline_queue.prioridad IS 'Prioridad de sincronizacion (1=critica, 4=baja)';
COMMENT ON COLUMN offline.offline_queue.payload IS 'Datos de la operacion en formato JSON';
COMMENT ON COLUMN offline.offline_queue.endpoint_destino IS 'URL o endpoint API destino para la sincronizacion';
COMMENT ON COLUMN offline.offline_queue.creado_offline_en IS 'Timestamp original cuando la operacion fue creada offline';
COMMENT ON COLUMN offline.offline_queue.cliente_id IS 'UUID generado en el cliente para deduplicacion de operaciones';
COMMENT ON COLUMN offline.offline_queue.intentos_sync IS 'Numero de intentos de sincronizacion realizados';
COMMENT ON COLUMN offline.offline_queue.historial_errores IS 'Historial de errores como array JSON [{timestamp, error}]';
COMMENT ON COLUMN offline.offline_queue.resolucion_conflicto IS 'Estrategia de resolucion: CLIENT_WINS, SERVER_WINS, MERGE, MANUAL';
COMMENT ON COLUMN offline.offline_queue.comprimido IS 'Indica si el payload esta comprimido (gzip)';

-- =============================================================================
-- FUNCIONES AUXILIARES
-- =============================================================================

-- Funcion para obtener operaciones pendientes ordenadas por prioridad
CREATE OR REPLACE FUNCTION offline.get_pending_operations(
  p_tenant_id UUID,
  p_limit INT DEFAULT 100
)
RETURNS TABLE (
  id UUID,
  tipo_operacion offline.tipo_operacion_offline,
  prioridad offline.prioridad_sync,
  payload JSONB,
  endpoint_destino VARCHAR(255),
  metodo_http VARCHAR(10),
  creado_offline_en TIMESTAMPTZ,
  intentos_sync INT
) AS $$
BEGIN
  RETURN QUERY
  SELECT
    oq.id,
    oq.tipo_operacion,
    oq.prioridad,
    oq.payload,
    oq.endpoint_destino,
    oq.metodo_http,
    oq.creado_offline_en,
    oq.intentos_sync
  FROM offline.offline_queue oq
  WHERE oq.tenant_id = p_tenant_id
    AND oq.estado IN ('PENDIENTE', 'ERROR')
    AND oq.intentos_sync < oq.max_intentos
  ORDER BY oq.prioridad ASC, oq.creado_offline_en ASC
  LIMIT p_limit;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Funcion para marcar operacion como en proceso
CREATE OR REPLACE FUNCTION offline.start_sync_operation(
  p_operation_id UUID
)
RETURNS BOOLEAN AS $$
DECLARE
  v_updated INT;
BEGIN
  UPDATE offline.offline_queue
  SET
    estado = 'EN_PROCESO',
    ultimo_intento_en = NOW(),
    intentos_sync = intentos_sync + 1
  WHERE id = p_operation_id
    AND estado IN ('PENDIENTE', 'ERROR');

  GET DIAGNOSTICS v_updated = ROW_COUNT;
  RETURN v_updated > 0;
END;
$$ LANGUAGE plpgsql;

-- Funcion para completar operacion exitosamente
CREATE OR REPLACE FUNCTION offline.complete_sync_operation(
  p_operation_id UUID
)
RETURNS BOOLEAN AS $$
DECLARE
  v_updated INT;
BEGIN
  UPDATE offline.offline_queue
  SET
    estado = 'COMPLETADO',
    sincronizado_en = NOW()
  WHERE id = p_operation_id
    AND estado = 'EN_PROCESO';

  GET DIAGNOSTICS v_updated = ROW_COUNT;
  RETURN v_updated > 0;
END;
$$ LANGUAGE plpgsql;

-- Funcion para registrar error en operacion
CREATE OR REPLACE FUNCTION offline.fail_sync_operation(
  p_operation_id UUID,
  p_error_message TEXT
)
RETURNS BOOLEAN AS $$
DECLARE
  v_updated INT;
  v_historial JSONB;
BEGIN
  -- Obtener historial actual
  SELECT historial_errores INTO v_historial
  FROM offline.offline_queue
  WHERE id = p_operation_id;

  -- Agregar nuevo error al historial
  v_historial = v_historial || jsonb_build_object(
    'timestamp', NOW()::TEXT,
    'error', p_error_message
  );

  UPDATE offline.offline_queue
  SET
    estado = 'ERROR',
    ultimo_error = p_error_message,
    historial_errores = v_historial
  WHERE id = p_operation_id
    AND estado = 'EN_PROCESO';

  GET DIAGNOSTICS v_updated = ROW_COUNT;
  RETURN v_updated > 0;
END;
$$ LANGUAGE plpgsql;

-- Funcion para limpiar operaciones antiguas completadas
CREATE OR REPLACE FUNCTION offline.cleanup_old_operations(
  p_days_to_keep INT DEFAULT 30
)
RETURNS INT AS $$
DECLARE
  v_deleted INT;
BEGIN
  DELETE FROM offline.offline_queue
  WHERE estado = 'COMPLETADO'
    AND sincronizado_en < NOW() - (p_days_to_keep || ' days')::INTERVAL;

  GET DIAGNOSTICS v_deleted = ROW_COUNT;
  RETURN v_deleted;
END;
$$ LANGUAGE plpgsql;

-- =============================================================================
-- GRANTS
-- =============================================================================

-- Permisos para funciones
GRANT EXECUTE ON FUNCTION offline.get_pending_operations(UUID, INT) TO PUBLIC;
GRANT EXECUTE ON FUNCTION offline.start_sync_operation(UUID) TO PUBLIC;
GRANT EXECUTE ON FUNCTION offline.complete_sync_operation(UUID) TO PUBLIC;
GRANT EXECUTE ON FUNCTION offline.fail_sync_operation(UUID, TEXT) TO PUBLIC;
GRANT EXECUTE ON FUNCTION offline.cleanup_old_operations(INT) TO PUBLIC;

-- =============================================================================
-- FIN DDL OFFLINE
-- =============================================================================
