--
-- This file is part of TALER
-- Copyright (C) 2025 Taler Systems SA
--
-- TALER is free software; you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
-- Foundation; either version 3, or (at your option) any later version.
--
-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with
-- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
--
-- @author Özgür Kesim

DROP FUNCTION IF EXISTS exchange_do_refresh;

CREATE FUNCTION exchange_do_refresh(
  IN in_rc BYTEA,
  IN in_now INT8,
  IN in_refresh_seed BYTEA,
  IN in_planchets_h BYTEA,
  IN in_amount_with_fee taler_amount,
  IN in_blinding_seed BYTEA,
  IN in_cs_r_values BYTEA[],
  IN in_cs_r_choices INT8,
  IN in_selected_h BYTEA,
  IN in_denom_sigs BYTEA[],
  IN in_denom_serials INT8[],
  IN in_old_coin_pub BYTEA,
  IN in_old_coin_sig BYTEA,
  IN in_noreveal_index INT4,
  IN in_zombie_required BOOLEAN,
  OUT out_coin_found BOOLEAN,
  OUT out_balance_ok BOOLEAN,
  OUT out_zombie_bad BOOLEAN,
  OUT out_nonce_reuse BOOLEAN,
  OUT out_idempotent BOOLEAN,
  OUT out_noreveal_index INT4,
  OUT out_coin_balance taler_amount)
LANGUAGE plpgsql
AS $$
DECLARE
  known_coin RECORD;
  difference RECORD;
BEGIN
-- Shards: INSERT refresh (by rc)
-- (rare:) SELECT refresh (by old_coin_pub) -- crosses shards!
-- (rare:) SELECT refresh_revealed_coins (by refresh_id)
-- (rare:) PERFORM recoup_refresh (by rrc_serial) -- crosses shards!
--         UPDATE known_coins (by coin_pub)

-- First, find old coin
SELECT known_coin_id
     ,remaining
  INTO known_coin
  FROM known_coins
  WHERE coin_pub = in_old_coin_pub;

IF NOT FOUND
THEN
  out_coin_found = FALSE;
  out_balance_ok = TRUE;
  out_zombie_bad = FALSE;
  out_nonce_reuse = FALSE;
  out_idempotent = FALSE;
  out_noreveal_index = -1 ;
  out_coin_balance.val = 0;
  out_coin_balance.frac = 0;
  RETURN;
END IF;

out_coin_found = TRUE;
out_coin_balance = known_coin.remaining;

-- Next, check for idempotency
SELECT TRUE, noreveal_index
INTO out_idempotent, out_noreveal_index
FROM exchange.refresh
WHERE rc=in_rc;

IF out_idempotent
THEN
  -- out_idempotent is set
  -- out_noreveal_index is set
  -- out_coin_found is set
  -- out_coin_balance is set
  out_balance_ok = TRUE;
  out_zombie_bad = FALSE; -- zombie is OK
  out_nonce_reuse = FALSE;
RETURN;
END IF;

out_idempotent = FALSE;
out_noreveal_index = in_noreveal_index;

-- Ensure the uniqueness of the blinding_seed
IF in_blinding_seed IS NOT NULL
THEN
  INSERT INTO unique_refresh_blinding_seed
    (blinding_seed)
  VALUES
    (in_blinding_seed)
  ON CONFLICT DO NOTHING;

  IF NOT FOUND
  THEN
      out_nonce_reuse = TRUE;
      out_balance_ok = TRUE;
      out_zombie_bad = FALSE; -- zombie is OK
      RETURN;
  END IF;
END IF;

out_nonce_reuse = FALSE;

INSERT INTO exchange.refresh
  (rc
  ,execution_date
  ,old_coin_pub
  ,old_coin_sig
  ,planchets_h
  ,amount_with_fee
  ,noreveal_index
  ,refresh_seed
  ,blinding_seed
  ,cs_r_values
  ,cs_r_choices
  ,selected_h
  ,denom_sigs
  ,denom_serials
  )
  VALUES
  (in_rc
  ,in_now
  ,in_old_coin_pub
  ,in_old_coin_sig
  ,in_planchets_h
  ,in_amount_with_fee
  ,in_noreveal_index
  ,in_refresh_seed
  ,in_blinding_seed
  ,in_cs_r_values
  ,in_cs_r_choices
  ,in_selected_h
  ,in_denom_sigs
  ,in_denom_serials
  )
  ON CONFLICT DO NOTHING;

IF NOT FOUND
THEN
  RAISE EXCEPTION 'Conflict in refresh despite idempotency check for rc(%)!', rc;
  RETURN;
END IF;

IF in_zombie_required
THEN
  -- Check if this coin was part of a refresh
  -- operation that was subsequently involved
  -- in a recoup operation.  We begin by all
  -- refresh operations our coin was involved
  -- with, then find all associated reveal
  -- operations, and then see if any of these
  -- reveal operations was involved in a recoup.
  PERFORM
   FROM recoup_refresh
   WHERE refresh_id IN
      (SELECT refresh_id
       FROM refresh
       WHERE old_coin_pub=in_old_coin_pub);
  IF NOT FOUND
  THEN
    out_zombie_bad=TRUE;
    out_balance_ok=FALSE;
    RETURN;
  END IF;
END IF;

out_zombie_bad=FALSE; -- zombie is OK

-- Check coin balance is sufficient.
SELECT *
INTO difference
FROM amount_left_minus_right(out_coin_balance
                            ,in_amount_with_fee);

out_balance_ok = difference.ok;

IF NOT out_balance_ok
THEN
  RETURN;
END IF;

out_coin_balance = difference.diff;

-- Check and update balance of the coin.
UPDATE known_coins
  SET
    remaining = out_coin_balance
  WHERE
    known_coin_id = known_coin.known_coin_id;

END $$;
