-- =====================================================
-- ORBIQUANT IA - UPDATE WALLET BALANCE FUNCTION
-- =====================================================
-- Description: Safely update wallet balance with audit trail
-- Schema: financial
-- =====================================================

CREATE OR REPLACE FUNCTION financial.update_wallet_balance(
  p_wallet_id UUID,
  p_amount DECIMAL(20,8),
  p_operation VARCHAR(20), -- 'add', 'subtract', 'set'
  p_transaction_id UUID DEFAULT NULL,
  p_actor_id UUID DEFAULT NULL,
  p_actor_type VARCHAR(50) DEFAULT 'system',
  p_reason TEXT DEFAULT NULL,
  p_metadata JSONB DEFAULT '{}'
)
RETURNS TABLE (
  success BOOLEAN,
  new_balance DECIMAL(20,8),
  new_available DECIMAL(20,8),
  error_message TEXT
)
LANGUAGE plpgsql
AS $$
DECLARE
  v_wallet RECORD;
  v_old_balance DECIMAL(20,8);
  v_old_available DECIMAL(20,8);
  v_new_balance DECIMAL(20,8);
  v_new_available DECIMAL(20,8);
BEGIN
  -- Lock wallet row for update
  SELECT * INTO v_wallet
  FROM financial.wallets
  WHERE id = p_wallet_id
  FOR UPDATE;

  -- Validar que existe
  IF NOT FOUND THEN
    RETURN QUERY SELECT false, 0::DECIMAL, 0::DECIMAL, 'Wallet not found';
    RETURN;
  END IF;

  -- Validar que está activa
  IF v_wallet.status != 'active' THEN
    RETURN QUERY SELECT false, v_wallet.balance, v_wallet.available_balance,
      'Wallet is not active (status: ' || v_wallet.status::TEXT || ')';
    RETURN;
  END IF;

  -- Guardar valores antiguos
  v_old_balance := v_wallet.balance;
  v_old_available := v_wallet.available_balance;

  -- Calcular nuevo balance según operación
  CASE p_operation
    WHEN 'add' THEN
      v_new_balance := v_old_balance + p_amount;
      v_new_available := v_old_available + p_amount;
    WHEN 'subtract' THEN
      v_new_balance := v_old_balance - p_amount;
      v_new_available := v_old_available - p_amount;
    WHEN 'set' THEN
      v_new_balance := p_amount;
      v_new_available := p_amount - v_wallet.pending_balance;
    ELSE
      RETURN QUERY SELECT false, v_old_balance, v_old_available,
        'Invalid operation: ' || p_operation;
      RETURN;
  END CASE;

  -- Validar que no quede negativo
  IF v_new_balance < 0 THEN
    RETURN QUERY SELECT false, v_old_balance, v_old_available,
      'Insufficient balance (current: ' || v_old_balance::TEXT || ', required: ' || p_amount::TEXT || ')';
    RETURN;
  END IF;

  IF v_new_available < 0 THEN
    RETURN QUERY SELECT false, v_old_balance, v_old_available,
      'Insufficient available balance (current: ' || v_old_available::TEXT || ')';
    RETURN;
  END IF;

  -- Validar min_balance si existe
  IF v_wallet.min_balance IS NOT NULL AND v_new_available < v_wallet.min_balance THEN
    RETURN QUERY SELECT false, v_old_balance, v_old_available,
      'Would violate minimum balance requirement (min: ' || v_wallet.min_balance::TEXT || ')';
    RETURN;
  END IF;

  -- Actualizar wallet
  UPDATE financial.wallets
  SET
    balance = v_new_balance,
    available_balance = v_new_available,
    last_transaction_at = NOW(),
    updated_at = NOW()
  WHERE id = p_wallet_id;

  -- Registrar en audit log
  INSERT INTO financial.wallet_audit_log (
    wallet_id,
    action,
    actor_id,
    actor_type,
    old_values,
    new_values,
    balance_before,
    balance_after,
    transaction_id,
    reason,
    metadata
  ) VALUES (
    p_wallet_id,
    'balance_updated',
    p_actor_id,
    p_actor_type,
    jsonb_build_object(
      'balance', v_old_balance,
      'available_balance', v_old_available
    ),
    jsonb_build_object(
      'balance', v_new_balance,
      'available_balance', v_new_available
    ),
    v_old_balance,
    v_new_balance,
    p_transaction_id,
    p_reason,
    p_metadata
  );

  -- Retornar éxito
  RETURN QUERY SELECT true, v_new_balance, v_new_available, NULL::TEXT;
END;
$$;

COMMENT ON FUNCTION financial.update_wallet_balance IS 'Safely update wallet balance with validation and audit trail';

-- Función helper para reservar fondos (pending balance)
CREATE OR REPLACE FUNCTION financial.reserve_wallet_funds(
  p_wallet_id UUID,
  p_amount DECIMAL(20,8),
  p_reason TEXT DEFAULT NULL
)
RETURNS TABLE (
  success BOOLEAN,
  new_available DECIMAL(20,8),
  new_pending DECIMAL(20,8),
  error_message TEXT
)
LANGUAGE plpgsql
AS $$
DECLARE
  v_wallet RECORD;
BEGIN
  -- Lock wallet
  SELECT * INTO v_wallet
  FROM financial.wallets
  WHERE id = p_wallet_id
  FOR UPDATE;

  IF NOT FOUND THEN
    RETURN QUERY SELECT false, 0::DECIMAL, 0::DECIMAL, 'Wallet not found';
    RETURN;
  END IF;

  IF v_wallet.status != 'active' THEN
    RETURN QUERY SELECT false, v_wallet.available_balance, v_wallet.pending_balance,
      'Wallet is not active';
    RETURN;
  END IF;

  IF v_wallet.available_balance < p_amount THEN
    RETURN QUERY SELECT false, v_wallet.available_balance, v_wallet.pending_balance,
      'Insufficient available balance';
    RETURN;
  END IF;

  -- Mover de available a pending
  UPDATE financial.wallets
  SET
    available_balance = available_balance - p_amount,
    pending_balance = pending_balance + p_amount,
    updated_at = NOW()
  WHERE id = p_wallet_id;

  -- Audit log
  INSERT INTO financial.wallet_audit_log (
    wallet_id, action, actor_type, reason,
    old_values, new_values
  ) VALUES (
    p_wallet_id, 'balance_updated', 'system', p_reason,
    jsonb_build_object('available', v_wallet.available_balance, 'pending', v_wallet.pending_balance),
    jsonb_build_object('available', v_wallet.available_balance - p_amount, 'pending', v_wallet.pending_balance + p_amount)
  );

  RETURN QUERY SELECT
    true,
    v_wallet.available_balance - p_amount,
    v_wallet.pending_balance + p_amount,
    NULL::TEXT;
END;
$$;

COMMENT ON FUNCTION financial.reserve_wallet_funds IS 'Reserve funds by moving from available to pending balance';

-- Función helper para liberar fondos reservados
CREATE OR REPLACE FUNCTION financial.release_wallet_funds(
  p_wallet_id UUID,
  p_amount DECIMAL(20,8),
  p_to_available BOOLEAN DEFAULT true,
  p_reason TEXT DEFAULT NULL
)
RETURNS TABLE (
  success BOOLEAN,
  new_available DECIMAL(20,8),
  new_pending DECIMAL(20,8),
  error_message TEXT
)
LANGUAGE plpgsql
AS $$
DECLARE
  v_wallet RECORD;
  v_new_balance DECIMAL(20,8);
BEGIN
  -- Lock wallet
  SELECT * INTO v_wallet
  FROM financial.wallets
  WHERE id = p_wallet_id
  FOR UPDATE;

  IF NOT FOUND THEN
    RETURN QUERY SELECT false, 0::DECIMAL, 0::DECIMAL, 'Wallet not found';
    RETURN;
  END IF;

  IF v_wallet.pending_balance < p_amount THEN
    RETURN QUERY SELECT false, v_wallet.available_balance, v_wallet.pending_balance,
      'Insufficient pending balance';
    RETURN;
  END IF;

  -- Liberar fondos
  IF p_to_available THEN
    -- Devolver a available
    v_new_balance := v_wallet.balance;
    UPDATE financial.wallets
    SET
      available_balance = available_balance + p_amount,
      pending_balance = pending_balance - p_amount,
      updated_at = NOW()
    WHERE id = p_wallet_id;
  ELSE
    -- Remover completamente (ej: después de withdrawal exitoso)
    v_new_balance := v_wallet.balance - p_amount;
    UPDATE financial.wallets
    SET
      balance = balance - p_amount,
      pending_balance = pending_balance - p_amount,
      updated_at = NOW()
    WHERE id = p_wallet_id;
  END IF;

  -- Audit log
  INSERT INTO financial.wallet_audit_log (
    wallet_id, action, actor_type, reason, metadata
  ) VALUES (
    p_wallet_id, 'balance_updated', 'system', p_reason,
    jsonb_build_object('released_amount', p_amount, 'to_available', p_to_available)
  );

  SELECT available_balance, pending_balance INTO v_wallet
  FROM financial.wallets
  WHERE id = p_wallet_id;

  RETURN QUERY SELECT true, v_wallet.available_balance, v_wallet.pending_balance, NULL::TEXT;
END;
$$;

COMMENT ON FUNCTION financial.release_wallet_funds IS 'Release reserved funds back to available or remove from balance';
