--
-- This file is part of TALER
-- Copyright (C) 2014--2026 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/>
--

DROP FUNCTION IF EXISTS exchange_do_import_credits;
CREATE FUNCTION exchange_do_import_credits(
  -- All credits in one call come from one bank account and one HTTP
  -- response, so the account and the reserve lifetimes are scalars.
  IN in_exchange_account_name TEXT,
  IN in_gc_date INT8,
  IN in_reserve_expiration INT8,
  -- Transfers into reserves.
  IN ina_reserve_pub BYTEA[],
  IN ina_wire_ref INT8[],
  IN ina_credit taler_amount[],
  IN ina_execution_date INT8[],
  IN ina_wire_source_h_payto BYTEA[],
  IN ina_h_normalized_payto BYTEA[],
  IN ina_payto_uri TEXT[],
  IN ina_notify TEXT[],
  -- KYC authentication transfers.
  IN ina_ka_account_pub BYTEA[],
  IN ina_ka_wire_ref INT8[],
  IN ina_ka_credit taler_amount[],
  IN ina_ka_execution_date INT8[],
  IN ina_ka_wire_source_h_payto BYTEA[],
  IN ina_ka_h_normalized_payto BYTEA[],
  IN ina_ka_payto_uri TEXT[],
  IN ina_ka_notify TEXT[],
  -- WAD transfers from other exchanges.
  IN ina_wad_id BYTEA[],
  IN ina_wad_origin_exchange_url TEXT[],
  IN ina_wad_amount taler_amount[],
  IN ina_wad_execution_date INT8[],
  -- Which shard this batch belongs to, and how far it takes us.
  IN in_job_name TEXT,
  IN in_shard_start INT8,
  IN in_shard_end INT8,
  IN in_progress_row INT8,
  IN in_lease_until INT8)
RETURNS TABLE (out_duplicate BOOLEAN)
LANGUAGE plpgsql
AS $$
DECLARE
  conflict BOOL;
  dup BOOL;
  uuid INT8;
  i INT4;
  my_is_wallet BOOL;
BEGIN

  -- === transfers into reserves ============================================
  FOR i IN 1..COALESCE(array_length(ina_reserve_pub,1),0)
  LOOP
    my_is_wallet
      = (LOWER (SUBSTRING (ina_payto_uri[i], 0, 23)) =
         'payto://taler-reserve/') OR
        (LOWER (SUBSTRING (ina_payto_uri[i], 0, 28)) =
         'payto://taler-reserve-http/');
    INSERT INTO kyc_targets
      (h_normalized_payto
      ,is_wallet
      ) VALUES (
       ina_h_normalized_payto[i]
      ,my_is_wallet
      )
      ON CONFLICT DO NOTHING;
    INSERT INTO wire_targets
      (wire_target_h_payto
      ,h_normalized_payto
      ,payto_uri
      ) VALUES (
       ina_wire_source_h_payto[i]
      ,ina_h_normalized_payto[i]
      ,ina_payto_uri[i]
      )
      ON CONFLICT DO NOTHING;

    INSERT INTO reserves
      (reserve_pub
      ,current_balance
      ,expiration_date
      ,gc_date
      ) VALUES (
       ina_reserve_pub[i]
      ,ina_credit[i]
      ,in_reserve_expiration
      ,in_gc_date
      )
      ON CONFLICT DO NOTHING
      RETURNING reserve_uuid
        INTO uuid;
    conflict = NOT FOUND;

    INSERT INTO reserves_in
      (reserve_pub
      ,wire_reference
      ,credit
      ,exchange_account_section
      ,wire_source_h_payto
      ,execution_date
      ) VALUES (
       ina_reserve_pub[i]
      ,ina_wire_ref[i]
      ,ina_credit[i]
      ,in_exchange_account_name
      ,ina_wire_source_h_payto[i]
      ,ina_execution_date[i]
      )
      ON CONFLICT DO NOTHING;

    IF NOT FOUND
    THEN
      IF conflict
      THEN
        dup = TRUE;
      ELSE
        dup = FALSE;
      END IF;
    ELSE
      IF NOT conflict
      THEN
        EXECUTE FORMAT (
          'NOTIFY %s'
          ,ina_notify[i]);
      END IF;
      dup = FALSE;
    END IF;

    IF (conflict AND NOT dup)
    THEN
      -- The reserve already existed, so the INSERT INTO reserves above did
      -- nothing and left the transfer uncredited.  Add it to the balance the
      -- reserve already has.
      --
      -- Because reserves_in.reserve_pub is that table's primary key, getting
      -- here at all means the reserve had no reserves_in row: a reserve that
      -- exists without ever having been wired to, i.e. one created by a purse
      -- merge.  The predecessor of this branch tried to INSERT that row a
      -- second time and treated the resulting conflict as "already imported",
      -- so the credit was recorded in reserves_in and then never added to
      -- current_balance.
      UPDATE reserves rs
        SET
           current_balance.frac = (rs.current_balance).frac+(ina_credit[i]).frac
             - CASE
               WHEN (rs.current_balance).frac + (ina_credit[i]).frac >= 100000000
                 THEN 100000000
                 ELSE 0
               END
          ,current_balance.val = (rs.current_balance).val+(ina_credit[i]).val
             + CASE
               WHEN (rs.current_balance).frac + (ina_credit[i]).frac >= 100000000
                 THEN 1
                 ELSE 0
               END
          ,expiration_date=GREATEST(expiration_date,in_reserve_expiration)
          ,gc_date=GREATEST(gc_date,in_gc_date)
        WHERE reserve_pub=ina_reserve_pub[i];
      EXECUTE FORMAT (
        'NOTIFY %s'
        ,ina_notify[i]);
    END IF;

    out_duplicate = dup;
    RETURN NEXT;
  END LOOP;

  -- === KYC authentication transfers =======================================
  FOR i IN 1..COALESCE(array_length(ina_ka_account_pub,1),0)
  LOOP
    INSERT INTO kycauths_in
      (account_pub
      ,wire_reference
      ,credit
      ,wire_source_h_payto
      ,exchange_account_section
      ,execution_date
      ) VALUES (
       ina_ka_account_pub[i]
      ,ina_ka_wire_ref[i]
      ,ina_ka_credit[i]
      ,ina_ka_wire_source_h_payto[i]
      ,in_exchange_account_name
      ,ina_ka_execution_date[i]
      )
      ON CONFLICT DO NOTHING;

    IF NOT FOUND
    THEN
      -- presumably already done
      CONTINUE;
    END IF;

    UPDATE kyc_targets
       SET target_pub=ina_ka_account_pub[i]
     WHERE h_normalized_payto=ina_ka_h_normalized_payto[i];

    IF NOT FOUND
    THEN
      -- First time we see this account, setup everything.
      my_is_wallet
        = (LOWER (SUBSTRING (ina_ka_payto_uri[i], 0, 23)) =
           'payto://taler-reserve/') OR
          (LOWER (SUBSTRING (ina_ka_payto_uri[i], 0, 28)) =
           'payto://taler-reserve-http/');
      INSERT INTO kyc_targets
        (h_normalized_payto
        ,is_wallet
        ,target_pub
        ) VALUES (
         ina_ka_h_normalized_payto[i]
        ,my_is_wallet
        ,ina_ka_account_pub[i]);
      INSERT INTO wire_targets
        (wire_target_h_payto
        ,h_normalized_payto
        ,payto_uri
        ) VALUES (
         ina_ka_wire_source_h_payto[i]
        ,ina_ka_h_normalized_payto[i]
        ,ina_ka_payto_uri[i]);
    END IF;

    EXECUTE FORMAT (
       'NOTIFY %s'
      ,ina_ka_notify[i]);
  END LOOP;

  -- === WAD transfers ======================================================
  FOR i IN 1..COALESCE(array_length(ina_wad_id,1),0)
  LOOP
    -- ON CONFLICT DO NOTHING is what the per-row INSERT this replaces
    -- effectively did: it let the unique violation through to libgnunetpq,
    -- which reports 23505 as "no results" and the caller shrugged it off.
    -- Inside a function an unhandled violation would take the whole batch
    -- down instead, and re-importing a shard has to stay harmless.
    INSERT INTO wads_in
      (wad_id
      ,origin_exchange_url
      ,amount
      ,arrival_time
      ) VALUES (
       ina_wad_id[i]
      ,ina_wad_origin_exchange_url[i]
      ,ina_wad_amount[i]
      ,ina_wad_execution_date[i]
      )
      ON CONFLICT DO NOTHING;
  END LOOP;

  -- === shard bookkeeping ==================================================
  -- Deliberately part of this statement rather than a follow-up transaction:
  -- the imports above and the record of how far we got have to become
  -- visible together.  Marking the shard completed is the same update, so
  -- finishing a shard costs nothing extra either.
  UPDATE work_shards
     SET progress_row=GREATEST(progress_row,in_progress_row)
        ,completed=(GREATEST(progress_row,in_progress_row) >= end_row)
        ,last_attempt=in_lease_until
   WHERE job_name=in_job_name
     AND start_row=in_shard_start
     AND end_row=in_shard_end;

  RETURN;
END $$;

COMMENT ON FUNCTION exchange_do_import_credits
  IS 'Imports one bank credit history response (reserve, KYC authentication and WAD transfers alike) and advances the work shard it belongs to, all in one statement. Returns one row per entry of ina_reserve_pub, in order, saying whether that transfer had already been imported.';
