--
-- 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/>
--

-- @file tops-0001.sql
-- @brief special TOPS-specific (AML) rules to inject into an exchange
-- @author Christian Grothoff

-- Everything in one big transaction
BEGIN;

-- Check patch versioning is in place.
SELECT _v.register_patch('tops-0001', NULL, NULL);

-- Note: this NOT an accident: the schema MUST be named
-- using the filename prefix (and the name under --enable-custom of taler-exchange-dbinit).
CREATE SCHEMA IF NOT EXISTS tops;

SET search_path TO tops,exchange;

INSERT INTO exchange_statistic_interval_meta
  (origin
  ,slug
  ,description
  ,stype
  ,ranges
  ,precisions)
VALUES
-- this first one is just for testing right now
  ('tops' -- must match schema!
  ,'deposit-transactions'
  ,'number of (batch) deposits performed by this merchant, used to detect sudden increase in number of transactions'
  ,'number'
  ,ARRAY(SELECT generate_series (60*60*24*7, 60*60*24*7*52, 60*60*24*7)) -- weekly volume over the last year
  ,array_fill (60*60*24, ARRAY[52]) -- precision is per day
  ),
  ('tops' -- must match schema!
  ,'deposit-volume'
  ,'total amount deposited by this merchant in (batch) deposits including deposit fees, used to detect sudden increase in transaction volume'
  ,'amount'
  ,ARRAY(SELECT generate_series (60*60*24*7, 60*60*24*7*52, 60*60*24*7)) -- weekly volume over the last year
  ,array_fill (60*60*24, ARRAY[52]) -- precision is per day
  )
ON CONFLICT DO NOTHING;

INSERT INTO exchange_statistic_bucket_meta
  (origin
  ,slug
  ,description
  ,stype
  ,ranges
  ,ages)
VALUES
-- this first one is just for testing right now
  ('tops' -- must match schema!
  ,'deposit-transactions'
  ,'number of (batch) deposits performed by this merchant, used to detect sudden increase in number of transactions'
  ,'number'
  ,ARRAY['day'::statistic_range,'week']
  ,ARRAY[5,5]
  ),
  ('tops' -- must match schema!
  ,'deposit-volume'
  ,'total amount deposited by this merchant in (batch) deposits including deposit fees, used to detect sudden increase in transaction volume'
  ,'amount'
  ,ARRAY['day'::statistic_range,'week']
  ,ARRAY[5,5]
  )
ON CONFLICT DO NOTHING;

DROP FUNCTION IF EXISTS tops_deposit_statistics_trigger CASCADE;
CREATE FUNCTION tops_deposit_statistics_trigger()
RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
  my_h_payto BYTEA; -- normalized h_payto of target account
  my_rec RECORD;
  my_last_year taler_amount;  -- sum of deposits this year
  my_last_month taler_amount; -- sum of deposits this month
  my_old_rules RECORD;
  my_properties TEXT;
  my_investigate_property JSONB;
  my_measure_name TEXT;
  my_rules TEXT;
BEGIN
  SELECT wt.h_normalized_payto
    INTO my_h_payto
    FROM wire_targets wt
   WHERE wire_target_h_payto = NEW.wire_target_h_payto;

  CALL exchange_do_bump_amount_stat
    ('deposit-volume'
    ,my_h_payto
    ,CURRENT_TIMESTAMP(0)::TIMESTAMP
    ,NEW.total_amount);

-- FIXME: this is just for testing, I want to also check
-- the 'counter'-based functions.
  CALL exchange_do_bump_number_stat
    ('deposit-transactions'
    ,my_h_payto
    ,CURRENT_TIMESTAMP(0)::TIMESTAMP
    ,1);

  -- Get historical deposit volumes and extract the yearly and monthly
  -- interval statistic values from the result for the AML trigger check.
  FOR my_rec IN
    SELECT *
      FROM exchange_statistic_interval_amount_get(
         'deposit-volume'
        ,my_h_payto
      )
  LOOP
    IF (my_rec.range = 60*60*24*7*52)
    THEN
      my_last_year = my_rec.rvalue;
    END IF;
    IF (my_rec.range = 60*60*24*7*4)
    THEN
      my_last_month = my_rec.rvalue;
    END IF;
  END LOOP;
  -- Note: it is OK to ignore '.frac', as that cannot be significant.
  -- Also, we effectively exclude the current month's revenue from
  -- "last year" as otherwise the rule makes no sense.
  -- Finally, we define the "current month" always as the last 4 weeks,
  -- just like the "last year" is the last 52 weeks.
  IF (my_last_year.val < my_last_month.val * 2)
  THEN
    -- This is suspicious. => Flag account for AML review!
    --
    -- FIXME: we probably want to factor the code from
    -- this branch out into a generic
    -- function to trigger investigations at some point!
    --
    -- First, get existing rules and clear an 'is_active'
    -- flag, but ONLY if we are not _already_ investigating
    -- the account (as in the latter case, we'll do no INSERT).
    UPDATE legitimization_outcomes
       SET is_active=NOT to_investigate
     WHERE h_payto = my_h_payto
       AND is_active
     RETURNING jproperties
              ,new_measure_name
              ,jnew_rules
              ,to_investigate
     INTO my_old_rules;

    -- Note that if we have no active legitimization_outcome
    -- that means we are on default rules and the account
    -- did not cross KYC thresholds and thus we have no
    -- established business relationship. In this case, we
    -- do not care as the overall volume is insignificant.
    -- This also takes care of the case where a customer
    -- is new (and obviously the first few months are
    -- basically always above the inherently zero or near-zero
    -- transactions from the previous year).
    -- Thus, we only proceed IF FOUND.
    IF FOUND
    THEN
      my_properties = my_old_rules.jproperties;
      my_measure_name = my_old_rules.new_measure_name;
      my_rules = my_old_rules.jnew_rules;
      my_investigate_property = json_object(ARRAY['AML_INVESTIGATION_STATE',
                                                  'AML_INVESTIGATION_TRIGGER'],
                                            ARRAY['INVESTIATION_PENDING',
                                                  'DEPOSIT_ANOMALY']);
      IF my_properties IS NULL
      THEN
        my_properties = my_investigate_property::TEXT;
      ELSE
        my_properties = (my_properties::JSONB || my_investigate_property)::TEXT;
      END IF;

      -- Note: here we could in theory manipulate my_properties,
      -- say to set a note as to why the investigation was started.
      IF NOT my_old_rules.to_investigate
      THEN
        -- Only insert if 'to_investigate' was not already set.
        INSERT INTO legitimization_outcomes (
           h_payto
          ,decision_time
          ,expiration_time
          ,jproperties
          ,new_measure_name
          ,to_investigate
          ,is_active
          ,jnew_rules
         ) VALUES (
           my_h_payto
          ,my_now
          ,my_now + 366*24*60*60
          ,my_properties
          ,my_measure_name
          ,TRUE
          ,TRUE
          ,my_rules);
      END IF;
    END IF;
  END IF;
  RETURN NEW;
END $$;
COMMENT ON FUNCTION tops_deposit_statistics_trigger
  IS 'creates deposit statistics';

-- Whenever a deposit is made, call our trigger to bump statistics
CREATE TRIGGER tops_batch_deposits_on_insert
  AFTER INSERT
    ON batch_deposits
  FOR EACH ROW EXECUTE FUNCTION tops_deposit_statistics_trigger();



COMMIT;
