-- =============================================================
-- ARCHIVO: 04-mobile.sql
-- DESCRIPCION: Sesiones moviles, sincronizacion offline, push tokens
-- VERSION: 1.0.0
-- PROYECTO: ERP-Core V2
-- FECHA: 2026-01-10
-- =============================================================

-- =====================
-- SCHEMA: mobile
-- =====================
CREATE SCHEMA IF NOT EXISTS mobile;

-- =====================
-- TABLA: mobile_sessions
-- Sesiones activas de la aplicacion movil
-- =====================
CREATE TABLE IF NOT EXISTS mobile.mobile_sessions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    device_id UUID NOT NULL REFERENCES auth.devices(id) ON DELETE CASCADE,
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
    branch_id UUID REFERENCES core.branches(id),

    -- Estado de la sesion
    status VARCHAR(20) NOT NULL DEFAULT 'active', -- active, paused, expired, terminated

    -- Perfil activo
    active_profile_id UUID REFERENCES auth.user_profiles(id),
    active_profile_code VARCHAR(10),

    -- Modo de operacion
    is_offline_mode BOOLEAN DEFAULT FALSE,
    offline_since TIMESTAMPTZ,

    -- Sincronizacion
    last_sync_at TIMESTAMPTZ,
    pending_sync_count INTEGER DEFAULT 0,

    -- Ubicacion
    last_latitude DECIMAL(10, 8),
    last_longitude DECIMAL(11, 8),
    last_location_at TIMESTAMPTZ,

    -- Metadata
    app_version VARCHAR(20),
    platform VARCHAR(20), -- ios, android
    os_version VARCHAR(20),

    -- Tiempos
    started_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    last_activity_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMPTZ,
    ended_at TIMESTAMPTZ,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

-- Indices para mobile_sessions
CREATE INDEX IF NOT EXISTS idx_mobile_sessions_user ON mobile.mobile_sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_mobile_sessions_device ON mobile.mobile_sessions(device_id);
CREATE INDEX IF NOT EXISTS idx_mobile_sessions_tenant ON mobile.mobile_sessions(tenant_id);
CREATE INDEX IF NOT EXISTS idx_mobile_sessions_branch ON mobile.mobile_sessions(branch_id);
CREATE INDEX IF NOT EXISTS idx_mobile_sessions_active ON mobile.mobile_sessions(status) WHERE status = 'active';

-- =====================
-- TABLA: offline_sync_queue
-- Cola de operaciones pendientes de sincronizar
-- =====================
CREATE TABLE IF NOT EXISTS mobile.offline_sync_queue (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    device_id UUID NOT NULL REFERENCES auth.devices(id) ON DELETE CASCADE,
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
    session_id UUID REFERENCES mobile.mobile_sessions(id),

    -- Operacion
    entity_type VARCHAR(50) NOT NULL, -- sale, attendance, inventory_count, etc.
    entity_id UUID, -- ID local del registro
    operation VARCHAR(20) NOT NULL, -- create, update, delete

    -- Datos
    payload JSONB NOT NULL,
    metadata JSONB DEFAULT '{}',

    -- Orden y dependencias
    sequence_number BIGINT NOT NULL,
    depends_on UUID, -- ID de otra operacion que debe procesarse primero

    -- Estado
    status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending, processing, completed, failed, conflict

    -- Procesamiento
    retry_count INTEGER DEFAULT 0,
    max_retries INTEGER DEFAULT 3,
    last_error TEXT,
    processed_at TIMESTAMPTZ,

    -- Conflicto
    conflict_data JSONB,
    conflict_resolved_at TIMESTAMPTZ,
    conflict_resolution VARCHAR(20), -- local_wins, server_wins, merged, manual

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

-- Indices para offline_sync_queue
CREATE INDEX IF NOT EXISTS idx_offline_sync_user ON mobile.offline_sync_queue(user_id);
CREATE INDEX IF NOT EXISTS idx_offline_sync_device ON mobile.offline_sync_queue(device_id);
CREATE INDEX IF NOT EXISTS idx_offline_sync_tenant ON mobile.offline_sync_queue(tenant_id);
CREATE INDEX IF NOT EXISTS idx_offline_sync_status ON mobile.offline_sync_queue(status);
CREATE INDEX IF NOT EXISTS idx_offline_sync_sequence ON mobile.offline_sync_queue(device_id, sequence_number);
CREATE INDEX IF NOT EXISTS idx_offline_sync_pending ON mobile.offline_sync_queue(status, created_at) WHERE status = 'pending';

-- =====================
-- TABLA: sync_conflicts
-- Registro de conflictos de sincronizacion
-- =====================
CREATE TABLE IF NOT EXISTS mobile.sync_conflicts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    sync_queue_id UUID NOT NULL REFERENCES mobile.offline_sync_queue(id) ON DELETE CASCADE,
    user_id UUID NOT NULL REFERENCES auth.users(id),
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id),

    -- Tipo de conflicto
    conflict_type VARCHAR(30) NOT NULL, -- version_mismatch, deleted_on_server, concurrent_edit

    -- Datos en conflicto
    local_data JSONB NOT NULL,
    server_data JSONB NOT NULL,

    -- Resolucion
    resolution VARCHAR(20), -- local_wins, server_wins, merged, manual
    merged_data JSONB,
    resolved_by UUID REFERENCES auth.users(id),
    resolved_at TIMESTAMPTZ,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

-- Indices para sync_conflicts
CREATE INDEX IF NOT EXISTS idx_sync_conflicts_queue ON mobile.sync_conflicts(sync_queue_id);
CREATE INDEX IF NOT EXISTS idx_sync_conflicts_user ON mobile.sync_conflicts(user_id);
CREATE INDEX IF NOT EXISTS idx_sync_conflicts_unresolved ON mobile.sync_conflicts(resolved_at) WHERE resolved_at IS NULL;

-- =====================
-- TABLA: push_tokens
-- Tokens de notificaciones push
-- =====================
CREATE TABLE IF NOT EXISTS mobile.push_tokens (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    device_id UUID NOT NULL REFERENCES auth.devices(id) ON DELETE CASCADE,
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

    -- Token
    token TEXT NOT NULL,
    platform VARCHAR(20) NOT NULL, -- ios, android
    provider VARCHAR(30) NOT NULL DEFAULT 'firebase', -- firebase, apns, fcm

    -- Estado
    is_active BOOLEAN DEFAULT TRUE,
    is_valid BOOLEAN DEFAULT TRUE,
    invalid_reason TEXT,

    -- Topics suscritos
    subscribed_topics TEXT[] DEFAULT '{}',

    -- Ultima actividad
    last_used_at TIMESTAMPTZ,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,

    UNIQUE(device_id, platform)
);

-- Indices para push_tokens
CREATE INDEX IF NOT EXISTS idx_push_tokens_user ON mobile.push_tokens(user_id);
CREATE INDEX IF NOT EXISTS idx_push_tokens_device ON mobile.push_tokens(device_id);
CREATE INDEX IF NOT EXISTS idx_push_tokens_tenant ON mobile.push_tokens(tenant_id);
CREATE INDEX IF NOT EXISTS idx_push_tokens_active ON mobile.push_tokens(is_active, is_valid) WHERE is_active = TRUE AND is_valid = TRUE;

-- =====================
-- TABLA: push_notifications_log
-- Log de notificaciones enviadas
-- =====================
CREATE TABLE IF NOT EXISTS mobile.push_notifications_log (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

    -- Destino
    user_id UUID REFERENCES auth.users(id),
    device_id UUID REFERENCES auth.devices(id),
    push_token_id UUID REFERENCES mobile.push_tokens(id),

    -- Notificacion
    title VARCHAR(200) NOT NULL,
    body TEXT,
    data JSONB DEFAULT '{}',
    category VARCHAR(50), -- attendance, sale, inventory, alert, system

    -- Envio
    sent_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    provider_message_id VARCHAR(255),

    -- Estado
    status VARCHAR(20) NOT NULL DEFAULT 'sent', -- sent, delivered, failed, read
    delivered_at TIMESTAMPTZ,
    read_at TIMESTAMPTZ,
    error_message TEXT,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

-- Indices para push_notifications_log
CREATE INDEX IF NOT EXISTS idx_push_log_tenant ON mobile.push_notifications_log(tenant_id);
CREATE INDEX IF NOT EXISTS idx_push_log_user ON mobile.push_notifications_log(user_id);
CREATE INDEX IF NOT EXISTS idx_push_log_device ON mobile.push_notifications_log(device_id);
CREATE INDEX IF NOT EXISTS idx_push_log_created ON mobile.push_notifications_log(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_push_log_category ON mobile.push_notifications_log(category);

-- =====================
-- TABLA: payment_transactions
-- Transacciones de pago desde terminales moviles
-- =====================
CREATE TABLE IF NOT EXISTS mobile.payment_transactions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
    branch_id UUID REFERENCES core.branches(id),
    user_id UUID NOT NULL REFERENCES auth.users(id),
    device_id UUID REFERENCES auth.devices(id),

    -- Referencia al documento origen
    source_type VARCHAR(30) NOT NULL, -- sale, invoice, subscription
    source_id UUID NOT NULL,

    -- Terminal de pago
    terminal_provider VARCHAR(30) NOT NULL, -- clip, mercadopago, stripe
    terminal_id VARCHAR(100),

    -- Transaccion
    external_transaction_id VARCHAR(255),
    amount DECIMAL(12,2) NOT NULL,
    currency VARCHAR(3) DEFAULT 'MXN',
    tip_amount DECIMAL(12,2) DEFAULT 0,
    total_amount DECIMAL(12,2) NOT NULL,

    -- Metodo de pago
    payment_method VARCHAR(30) NOT NULL, -- card, contactless, qr, link
    card_brand VARCHAR(20), -- visa, mastercard, amex
    card_last_four VARCHAR(4),
    card_type VARCHAR(20), -- credit, debit

    -- Estado
    status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending, processing, completed, failed, refunded, cancelled
    failure_reason TEXT,

    -- Tiempos
    initiated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    completed_at TIMESTAMPTZ,

    -- Metadata del proveedor
    provider_response JSONB DEFAULT '{}',

    -- Recibo
    receipt_url TEXT,
    receipt_sent BOOLEAN DEFAULT FALSE,
    receipt_sent_to VARCHAR(255),

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

-- Indices para payment_transactions
CREATE INDEX IF NOT EXISTS idx_payment_tx_tenant ON mobile.payment_transactions(tenant_id);
CREATE INDEX IF NOT EXISTS idx_payment_tx_branch ON mobile.payment_transactions(branch_id);
CREATE INDEX IF NOT EXISTS idx_payment_tx_user ON mobile.payment_transactions(user_id);
CREATE INDEX IF NOT EXISTS idx_payment_tx_source ON mobile.payment_transactions(source_type, source_id);
CREATE INDEX IF NOT EXISTS idx_payment_tx_external ON mobile.payment_transactions(external_transaction_id);
CREATE INDEX IF NOT EXISTS idx_payment_tx_status ON mobile.payment_transactions(status);
CREATE INDEX IF NOT EXISTS idx_payment_tx_created ON mobile.payment_transactions(created_at DESC);

-- =====================
-- RLS POLICIES
-- =====================
ALTER TABLE mobile.mobile_sessions ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_mobile_sessions ON mobile.mobile_sessions
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

ALTER TABLE mobile.offline_sync_queue ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_sync_queue ON mobile.offline_sync_queue
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

ALTER TABLE mobile.sync_conflicts ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_sync_conflicts ON mobile.sync_conflicts
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

ALTER TABLE mobile.push_tokens ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_push_tokens ON mobile.push_tokens
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

ALTER TABLE mobile.push_notifications_log ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_push_log ON mobile.push_notifications_log
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

ALTER TABLE mobile.payment_transactions ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_payment_tx ON mobile.payment_transactions
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

-- =====================
-- FUNCIONES
-- =====================

-- Funcion para obtener siguiente numero de secuencia
CREATE OR REPLACE FUNCTION mobile.get_next_sync_sequence(p_device_id UUID)
RETURNS BIGINT AS $$
DECLARE
    next_seq BIGINT;
BEGIN
    SELECT COALESCE(MAX(sequence_number), 0) + 1
    INTO next_seq
    FROM mobile.offline_sync_queue
    WHERE device_id = p_device_id;

    RETURN next_seq;
END;
$$ LANGUAGE plpgsql;

-- Funcion para procesar cola de sincronizacion
CREATE OR REPLACE FUNCTION mobile.process_sync_queue(p_device_id UUID, p_batch_size INTEGER DEFAULT 100)
RETURNS TABLE (
    processed_count INTEGER,
    failed_count INTEGER,
    conflict_count INTEGER
) AS $$
DECLARE
    v_processed INTEGER := 0;
    v_failed INTEGER := 0;
    v_conflicts INTEGER := 0;
BEGIN
    -- Marcar items como processing (usando subquery para ORDER BY y LIMIT en PostgreSQL)
    UPDATE mobile.offline_sync_queue
    SET status = 'processing', updated_at = CURRENT_TIMESTAMP
    WHERE id IN (
        SELECT osq.id FROM mobile.offline_sync_queue osq
        WHERE osq.device_id = p_device_id
        AND osq.status = 'pending'
        AND (osq.depends_on IS NULL OR osq.depends_on IN (
            SELECT id FROM mobile.offline_sync_queue WHERE status = 'completed'
        ))
        ORDER BY osq.sequence_number
        LIMIT p_batch_size
    );

    -- Aqui iria la logica de procesamiento real
    -- Por ahora solo retornamos conteos

    SELECT COUNT(*) INTO v_processed
    FROM mobile.offline_sync_queue
    WHERE device_id = p_device_id AND status = 'processing';

    RETURN QUERY SELECT v_processed, v_failed, v_conflicts;
END;
$$ LANGUAGE plpgsql;

-- Funcion para limpiar sesiones inactivas
CREATE OR REPLACE FUNCTION mobile.cleanup_inactive_sessions(p_hours INTEGER DEFAULT 24)
RETURNS INTEGER AS $$
DECLARE
    cleaned_count INTEGER;
BEGIN
    UPDATE mobile.mobile_sessions
    SET status = 'expired', ended_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
    WHERE status = 'active'
    AND last_activity_at < CURRENT_TIMESTAMP - (p_hours || ' hours')::INTERVAL;

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

-- =====================
-- COMENTARIOS DE TABLAS
-- =====================
COMMENT ON TABLE mobile.mobile_sessions IS 'Sesiones activas de la aplicacion movil';
COMMENT ON TABLE mobile.offline_sync_queue IS 'Cola de operaciones pendientes de sincronizar desde modo offline';
COMMENT ON TABLE mobile.sync_conflicts IS 'Registro de conflictos de sincronizacion detectados';
COMMENT ON TABLE mobile.push_tokens IS 'Tokens de notificaciones push por dispositivo';
COMMENT ON TABLE mobile.push_notifications_log IS 'Log de notificaciones push enviadas';
COMMENT ON TABLE mobile.payment_transactions IS 'Transacciones de pago desde terminales moviles';
