-- 005_post_ledger_entry.sql
-- Atomic ledger posting function with balance updates
-- Enforces idempotency, hash chaining, and balance integrity at DB level

CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE OR REPLACE FUNCTION post_ledger_entry(
  p_ledger_id text,
  p_debit_account_id text,
  p_credit_account_id text,
  p_amount numeric,
  p_currency_code text,
  p_asset_type text,
  p_transaction_type text,
  p_reference_id text,
  p_fx_rate numeric DEFAULT NULL,
  p_metadata jsonb DEFAULT NULL
) RETURNS TABLE(
  entry_id text,
  block_hash text,
  debit_balance numeric,
  credit_balance numeric
) AS $$
DECLARE
  v_entry_id text := gen_random_uuid()::text;
  v_prev_hash text;
  v_now timestamptz := now();
  v_payload text;
  v_block_hash text;
  v_debit record;
  v_credit record;
  a1 text;
  a2 text;
BEGIN
  -- Validate amount
  IF p_amount IS NULL OR p_amount <= 0 THEN
    RAISE EXCEPTION 'Amount must be > 0';
  END IF;

  -- Idempotency check
  IF EXISTS (
    SELECT 1 FROM ledger_entries
    WHERE ledger_id = p_ledger_id
      AND reference_id = p_reference_id
  ) THEN
    RAISE EXCEPTION 'Duplicate reference_id for ledger: %', p_reference_id;
  END IF;

  -- Lock ledger stream (prevents hash-chain races)
  PERFORM pg_advisory_xact_lock(hashtext(p_ledger_id));

  -- Deadlock-safe lock ordering (always lock accounts in id order)
  a1 := LEAST(p_debit_account_id, p_credit_account_id);
  a2 := GREATEST(p_debit_account_id, p_credit_account_id);

  PERFORM 1 FROM bank_accounts WHERE id = a1 FOR UPDATE;
  PERFORM 1 FROM bank_accounts WHERE id = a2 FOR UPDATE;

  -- Fetch accounts (already locked)
  SELECT * INTO v_debit FROM bank_accounts WHERE id = p_debit_account_id;
  SELECT * INTO v_credit FROM bank_accounts WHERE id = p_credit_account_id;

  IF v_debit.id IS NULL OR v_credit.id IS NULL THEN
    RAISE EXCEPTION 'Account not found';
  END IF;

  -- Currency validation
  IF v_debit.currency_code <> p_currency_code OR v_credit.currency_code <> p_currency_code THEN
    RAISE EXCEPTION 'Currency mismatch';
  END IF;

  -- Sufficient funds check
  IF v_debit.available_balance < p_amount THEN
    RAISE EXCEPTION 'Insufficient balance: available=%, requested=%',
      v_debit.available_balance, p_amount;
  END IF;

  -- Get previous hash for chain
  SELECT block_hash INTO v_prev_hash
  FROM ledger_entries
  WHERE ledger_id = p_ledger_id
  ORDER BY timestamp_utc DESC
  LIMIT 1;

  -- Compute canonical payload for block hash
  v_payload :=
    COALESCE(v_prev_hash,'') || '|' ||
    v_entry_id || '|' ||
    p_ledger_id || '|' ||
    p_debit_account_id || '|' ||
    p_credit_account_id || '|' ||
    p_amount::text || '|' ||
    p_currency_code || '|' ||
    p_asset_type || '|' ||
    p_transaction_type || '|' ||
    p_reference_id || '|' ||
    v_now::text;

  -- Compute block hash
  v_block_hash := encode(digest(v_payload, 'sha256'), 'hex');

  -- Insert ledger entry
  INSERT INTO ledger_entries (
    id, ledger_id, debit_account_id, credit_account_id,
    amount, currency_code, fx_rate, asset_type, transaction_type,
    reference_id, timestamp_utc, block_hash, previous_hash,
    status, metadata, created_at, updated_at
  ) VALUES (
    v_entry_id, p_ledger_id, p_debit_account_id, p_credit_account_id,
    p_amount, p_currency_code, p_fx_rate, p_asset_type, p_transaction_type,
    p_reference_id, v_now, v_block_hash, v_prev_hash,
    'POSTED', p_metadata, v_now, v_now
  );

  -- Update balances atomically
  UPDATE bank_accounts
  SET balance = balance - p_amount,
      available_balance = available_balance - p_amount,
      updated_at = v_now
  WHERE id = p_debit_account_id;

  UPDATE bank_accounts
  SET balance = balance + p_amount,
      available_balance = available_balance + p_amount,
      updated_at = v_now
  WHERE id = p_credit_account_id;

  -- Return result
  RETURN QUERY
  SELECT
    v_entry_id,
    v_block_hash,
    (SELECT balance FROM bank_accounts WHERE id = p_debit_account_id),
    (SELECT balance FROM bank_accounts WHERE id = p_credit_account_id);
END;
$$ LANGUAGE plpgsql;
