-- =====================================================
-- ORBIQUANT IA - PROCESS TRANSACTION FUNCTION
-- =====================================================
-- Description: Create and process wallet transactions atomically
-- Schema: financial
-- =====================================================

CREATE OR REPLACE FUNCTION financial.process_transaction(
  p_wallet_id UUID,
  p_transaction_type financial.transaction_type,
  p_amount DECIMAL(20,8),
  p_currency financial.currency_code,
  p_fee DECIMAL(15,8) DEFAULT 0,
  p_description TEXT DEFAULT NULL,
  p_reference_id VARCHAR(100) DEFAULT NULL,
  p_destination_wallet_id UUID DEFAULT NULL,
  p_idempotency_key VARCHAR(255) DEFAULT NULL,
  p_metadata JSONB DEFAULT '{}',
  p_auto_complete BOOLEAN DEFAULT false
)
RETURNS TABLE (
  success BOOLEAN,
  transaction_id UUID,
  new_balance DECIMAL(20,8),
  error_message TEXT
)
LANGUAGE plpgsql
AS $$
DECLARE
  v_wallet RECORD;
  v_tx_id UUID;
  v_balance_before DECIMAL(20,8);
  v_balance_after DECIMAL(20,8);
  v_update_result RECORD;
  v_dest_tx_id UUID;
BEGIN
  -- Validar idempotency
  IF p_idempotency_key IS NOT NULL THEN
    SELECT id, wallet_transactions.status INTO v_tx_id, v_wallet
    FROM financial.wallet_transactions
    WHERE idempotency_key = p_idempotency_key;

    IF FOUND THEN
      -- Transacción ya existe
      SELECT balance INTO v_balance_after
      FROM financial.wallets
      WHERE id = p_wallet_id;

      RETURN QUERY SELECT
        true,
        v_tx_id,
        v_balance_after,
        'Transaction already exists (idempotent)'::TEXT;
      RETURN;
    END IF;
  END IF;

  -- Lock wallet
  SELECT * INTO v_wallet
  FROM financial.wallets
  WHERE id = p_wallet_id
  FOR UPDATE;

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

  IF v_wallet.status != 'active' THEN
    RETURN QUERY SELECT false, NULL::UUID, v_wallet.balance,
      'Wallet is not active (status: ' || v_wallet.status::TEXT || ')';
    RETURN;
  END IF;

  -- Validar currency match
  IF v_wallet.currency != p_currency THEN
    RETURN QUERY SELECT false, NULL::UUID, v_wallet.balance,
      'Currency mismatch (wallet: ' || v_wallet.currency::TEXT || ', transaction: ' || p_currency::TEXT || ')';
    RETURN;
  END IF;

  -- Validar destination para transfers
  IF p_transaction_type IN ('transfer_out', 'transfer_in') AND p_destination_wallet_id IS NULL THEN
    RETURN QUERY SELECT false, NULL::UUID, v_wallet.balance,
      'Transfer requires destination_wallet_id';
    RETURN;
  END IF;

  -- No permitir self-transfers
  IF p_destination_wallet_id = p_wallet_id THEN
    RETURN QUERY SELECT false, NULL::UUID, v_wallet.balance,
      'Cannot transfer to same wallet';
    RETURN;
  END IF;

  v_balance_before := v_wallet.balance;

  -- Crear transacción
  INSERT INTO financial.wallet_transactions (
    wallet_id,
    transaction_type,
    status,
    amount,
    fee,
    currency,
    balance_before,
    destination_wallet_id,
    reference_id,
    description,
    metadata,
    idempotency_key,
    processed_at
  ) VALUES (
    p_wallet_id,
    p_transaction_type,
    CASE WHEN p_auto_complete THEN 'completed'::financial.transaction_status
         ELSE 'pending'::financial.transaction_status END,
    p_amount,
    p_fee,
    p_currency,
    v_balance_before,
    p_destination_wallet_id,
    p_reference_id,
    p_description,
    p_metadata,
    p_idempotency_key,
    CASE WHEN p_auto_complete THEN NOW() ELSE NULL END
  )
  RETURNING id INTO v_tx_id;

  -- Si es auto_complete, procesar inmediatamente
  IF p_auto_complete THEN
    -- Determinar operación de balance
    CASE p_transaction_type
      WHEN 'deposit', 'transfer_in', 'earning', 'distribution', 'bonus', 'refund' THEN
        -- Aumentar balance
        SELECT * INTO v_update_result
        FROM financial.update_wallet_balance(
          p_wallet_id,
          p_amount - p_fee,
          'add',
          v_tx_id,
          NULL,
          'system',
          'Transaction: ' || p_transaction_type::TEXT
        );

      WHEN 'withdrawal', 'transfer_out', 'fee' THEN
        -- Disminuir balance
        SELECT * INTO v_update_result
        FROM financial.update_wallet_balance(
          p_wallet_id,
          p_amount + p_fee,
          'subtract',
          v_tx_id,
          NULL,
          'system',
          'Transaction: ' || p_transaction_type::TEXT
        );

      ELSE
        RETURN QUERY SELECT false, v_tx_id, v_balance_before,
          'Unknown transaction type: ' || p_transaction_type::TEXT;
        RETURN;
    END CASE;

    -- Verificar éxito de actualización
    IF NOT v_update_result.success THEN
      -- Marcar transacción como fallida
      UPDATE financial.wallet_transactions
      SET
        status = 'failed',
        failed_reason = v_update_result.error_message,
        failed_at = NOW()
      WHERE id = v_tx_id;

      RETURN QUERY SELECT false, v_tx_id, v_balance_before, v_update_result.error_message;
      RETURN;
    END IF;

    v_balance_after := v_update_result.new_balance;

    -- Actualizar balance_after en transacción
    UPDATE financial.wallet_transactions
    SET
      balance_after = v_balance_after,
      completed_at = NOW()
    WHERE id = v_tx_id;

    -- Si es transfer_out, crear transfer_in en destino
    IF p_transaction_type = 'transfer_out' AND p_destination_wallet_id IS NOT NULL THEN
      SELECT * INTO v_update_result
      FROM financial.process_transaction(
        p_destination_wallet_id,
        'transfer_in',
        p_amount - p_fee, -- El fee lo paga el origen
        p_currency,
        0, -- Sin fee adicional en destino
        'Transfer from wallet ' || p_wallet_id::TEXT,
        p_reference_id,
        p_wallet_id, -- Origen como destino inverso
        p_idempotency_key || '_dest', -- Idempotency para destino
        p_metadata,
        true -- Auto-complete
      );

      IF v_update_result.success THEN
        v_dest_tx_id := v_update_result.transaction_id;

        -- Vincular transacciones
        UPDATE financial.wallet_transactions
        SET related_transaction_id = v_dest_tx_id
        WHERE id = v_tx_id;

        UPDATE financial.wallet_transactions
        SET related_transaction_id = v_tx_id
        WHERE id = v_dest_tx_id;
      END IF;
    END IF;

    -- Actualizar totals en wallet
    IF p_transaction_type IN ('deposit', 'transfer_in') THEN
      UPDATE financial.wallets
      SET total_deposits = total_deposits + p_amount
      WHERE id = p_wallet_id;
    ELSIF p_transaction_type IN ('withdrawal', 'transfer_out') THEN
      UPDATE financial.wallets
      SET total_withdrawals = total_withdrawals + p_amount
      WHERE id = p_wallet_id;
    END IF;

  ELSE
    -- Transaction pending, no balance update yet
    v_balance_after := v_balance_before;
  END IF;

  RETURN QUERY SELECT true, v_tx_id, v_balance_after, NULL::TEXT;
END;
$$;

COMMENT ON FUNCTION financial.process_transaction IS 'Create and optionally complete a wallet transaction atomically';

-- Función para completar transacción pendiente
CREATE OR REPLACE FUNCTION financial.complete_transaction(
  p_transaction_id UUID
)
RETURNS TABLE (
  success BOOLEAN,
  new_balance DECIMAL(20,8),
  error_message TEXT
)
LANGUAGE plpgsql
AS $$
DECLARE
  v_tx RECORD;
  v_update_result RECORD;
BEGIN
  -- Lock transaction
  SELECT * INTO v_tx
  FROM financial.wallet_transactions
  WHERE id = p_transaction_id
  FOR UPDATE;

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

  IF v_tx.status != 'pending' THEN
    RETURN QUERY SELECT false, 0::DECIMAL,
      'Transaction is not pending (status: ' || v_tx.status::TEXT || ')';
    RETURN;
  END IF;

  -- Procesar según tipo
  CASE v_tx.transaction_type
    WHEN 'deposit', 'transfer_in', 'earning', 'distribution', 'bonus', 'refund' THEN
      SELECT * INTO v_update_result
      FROM financial.update_wallet_balance(
        v_tx.wallet_id,
        v_tx.amount - v_tx.fee,
        'add',
        p_transaction_id
      );

    WHEN 'withdrawal', 'transfer_out', 'fee' THEN
      SELECT * INTO v_update_result
      FROM financial.update_wallet_balance(
        v_tx.wallet_id,
        v_tx.amount + v_tx.fee,
        'subtract',
        p_transaction_id
      );

    ELSE
      RETURN QUERY SELECT false, 0::DECIMAL, 'Unknown transaction type';
      RETURN;
  END CASE;

  IF NOT v_update_result.success THEN
    -- Marcar como fallida
    UPDATE financial.wallet_transactions
    SET
      status = 'failed',
      failed_reason = v_update_result.error_message,
      failed_at = NOW()
    WHERE id = p_transaction_id;

    RETURN QUERY SELECT false, 0::DECIMAL, v_update_result.error_message;
    RETURN;
  END IF;

  -- Marcar como completada
  UPDATE financial.wallet_transactions
  SET
    status = 'completed',
    balance_after = v_update_result.new_balance,
    completed_at = NOW(),
    processed_at = COALESCE(processed_at, NOW())
  WHERE id = p_transaction_id;

  RETURN QUERY SELECT true, v_update_result.new_balance, NULL::TEXT;
END;
$$;

COMMENT ON FUNCTION financial.complete_transaction IS 'Complete a pending wallet transaction';
