--
-- This file is part of TALER
-- Copyright (C) 2023, 2024 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_insert_aml_decision;
CREATE FUNCTION exchange_do_insert_aml_decision(
  IN in_payto_uri TEXT, -- can be NULL!
  IN in_h_normalized_payto BYTEA,
  IN in_h_full_payto BYTEA, -- can be NULL!
  IN in_decision_time INT8,
  IN in_expiration_time INT8,
  IN in_properties JSONB, -- can be NULL
  IN in_kyc_attributes_enc BYTEA, -- can be NULL
  IN in_kyc_attributes_hash BYTEA, -- can be NULL
  IN in_kyc_attributes_expiration INT8, -- can be NULL
  IN in_new_rules JSONB,
  IN in_to_investigate BOOLEAN,
  IN in_new_measure_name TEXT, -- can be NULL
  IN in_jmeasures JSONB, -- can be NULL
  IN in_justification TEXT, -- can be NULL
  IN in_decider_pub BYTEA, -- can be NULL
  IN in_decider_sig BYTEA, -- can be NULL
  IN in_notify_s TEXT,
  IN ina_events TEXT[],
  IN in_form_name TEXT, -- can be NULL
  OUT out_invalid_officer BOOLEAN,
  OUT out_account_unknown BOOLEAN,
  OUT out_last_date INT8,
  OUT out_legitimization_measure_serial_id INT8,
  OUT out_is_wallet BOOL) -- can be (left at) NULL
LANGUAGE plpgsql
AS $$
DECLARE
  my_outcome_serial_id INT8;
  my_legitimization_process_serial_id INT8;
  my_kyc_attributes_serial_id INT8;
  my_rec RECORD;
  my_access_token BYTEA;
  my_i INT4;
  ini_event TEXT;
BEGIN

out_account_unknown=FALSE;
out_legitimization_measure_serial_id=0;

IF in_decider_pub IS NOT NULL
THEN
  IF in_justification IS NULL OR in_decider_sig IS NULL
  THEN
    RAISE EXCEPTION 'Got in_decider_sig without justification or signature.';
  END IF;
  -- Check officer is eligible to make decisions.
  PERFORM
    FROM aml_staff
    WHERE decider_pub=in_decider_pub
      AND is_active
      AND NOT read_only;
  IF NOT FOUND
  THEN
    out_invalid_officer=TRUE;
    out_last_date=0;
    RETURN;
  END IF;
END IF;

out_invalid_officer=FALSE;

-- Check no more recent decision exists.
SELECT decision_time
  INTO out_last_date
  FROM legitimization_outcomes
 WHERE h_payto=in_h_normalized_payto
   AND is_active
 ORDER BY decision_time DESC, outcome_serial_id DESC;

IF FOUND
THEN
  IF in_decider_pub IS NOT NULL AND out_last_date > in_decision_time
  THEN
    -- Refuse to insert older decision for officer decisions.
    RETURN;
  END IF;
  UPDATE legitimization_outcomes
     SET is_active=FALSE
   WHERE h_payto=in_h_normalized_payto
     AND is_active;
ELSE
  out_last_date = 0;
END IF;

SELECT access_token
      ,is_wallet
  INTO my_rec
  FROM kyc_targets
 WHERE h_normalized_payto=in_h_normalized_payto;

IF NOT FOUND
THEN
  -- AML decision for previously unknown account; better includes
  -- all required details about the account ...
  IF in_payto_uri IS NULL
  THEN
    -- AML decision on an unknown account without payto_uri => fail.
    out_account_unknown=TRUE;
    RETURN;
  END IF;
  -- Well, fine, setup the account
  out_is_wallet
    = (LOWER (SUBSTRING (in_payto_uri, 22)) =
       'payto://taler-reserve/') OR
      (LOWER (SUBSTRING (in_payto_uri, 27)) =
       'payto://taler-reserve-http/');
  INSERT INTO kyc_targets
    (h_normalized_payto
    ,is_wallet
    ) VALUES (
     in_h_normalized_payto
    ,out_is_wallet
    )
    RETURNING access_token
      INTO my_access_token;
  INSERT INTO wire_targets
    (wire_target_h_payto
    ,h_normalized_payto
    ,payto_uri
    ) VALUES (
     in_h_full_payto
    ,in_h_normalized_payto
    ,in_payto_uri
    )
    ON CONFLICT DO NOTHING;
ELSE
  my_access_token = my_rec.access_token;
  out_is_wallet = my_rec.is_wallet;
END IF;

-- Did KYC measures get prescribed?
IF in_jmeasures IS NOT NULL
THEN
  -- First check if a perfectly equivalent legi measure
  -- already exists, to avoid creating tons of duplicates.
  SELECT legitimization_measure_serial_id
    INTO out_legitimization_measure_serial_id
    FROM legitimization_measures
    WHERE access_token=my_access_token
      AND jmeasures=in_jmeasures
      AND NOT is_finished;

  IF NOT FOUND
  THEN
    -- Enable new legitimization measure
    INSERT INTO legitimization_measures
      (access_token
      ,start_time
      ,jmeasures
      ,display_priority
      ) VALUES (
       my_access_token
      ,in_decision_time
      ,in_jmeasures
      ,1)
      RETURNING
        legitimization_measure_serial_id
      INTO
        out_legitimization_measure_serial_id;
  END IF;
  -- end if for where we had in_jmeasures
END IF;

RAISE NOTICE 'marking legi measures of % as finished except for %', my_access_token, out_legitimization_measure_serial_id;

-- AML decision: mark all other active measures finished!
UPDATE legitimization_measures
  SET is_finished=TRUE
  WHERE access_token=my_access_token
    AND NOT is_finished
    AND legitimization_measure_serial_id != out_legitimization_measure_serial_id;

UPDATE legitimization_outcomes
   SET is_active=FALSE
 WHERE h_payto=in_h_normalized_payto
   -- this clause is a minor optimization to avoid
   -- updating outcomes that have long expired.
   AND expiration_time >= in_decision_time;

INSERT INTO legitimization_outcomes
  (h_payto
  ,decision_time
  ,expiration_time
  ,jproperties
  ,new_measure_name
  ,to_investigate
  ,jnew_rules
  ) VALUES (
   in_h_normalized_payto
  ,in_decision_time
  ,in_expiration_time
  ,in_properties
  ,in_new_measure_name
  ,in_to_investigate
  ,in_new_rules
  )
  RETURNING outcome_serial_id
       INTO my_outcome_serial_id;

IF in_kyc_attributes_enc IS NOT NULL
THEN
  IF in_kyc_attributes_hash IS NULL OR in_kyc_attributes_expiration IS NULL
  THEN
    RAISE EXCEPTION 'Got in_kyc_attributes_hash without hash or expiration.';
  END IF;
  IF in_decider_pub IS NULL
  THEN
    RAISE EXCEPTION 'Got in_kyc_attributes_hash without in_decider_pub.';
  END IF;
  -- Simulate a legi process for attribute insertion by AML Officer
  INSERT INTO legitimization_processes
    (h_payto
    ,start_time
    ,expiration_time
    ,provider_name
    ,provider_user_id
    ,finished
    ) VALUES (
     in_h_normalized_payto
    ,in_decision_time
    -- Process starts and finishes instantly
    ,in_decision_time
    ,'aml-officer'
    ,in_decider_pub
    ,TRUE
    )
    RETURNING legitimization_process_serial_id
    INTO my_legitimization_process_serial_id;
  -- Now we can insert the attribute!
  INSERT INTO kyc_attributes
    (h_payto
    ,collection_time
    ,expiration_time
    ,form_name
    ,by_aml_officer
    ,encrypted_attributes
    ,legitimization_serial
    ) VALUES (
     in_h_normalized_payto
    ,in_decision_time
    ,in_kyc_attributes_expiration
    ,in_form_name
    ,TRUE
    ,in_kyc_attributes_enc
    ,my_legitimization_process_serial_id
    )
    RETURNING kyc_attributes_serial_id
    INTO my_kyc_attributes_serial_id;
  -- Wake up taler-exchange-sanctionscheck to check new attributes
  -- This is value for TALER_DBEVENT_EXCHANGE_NEW_KYC_ATTRIBUTES.
  NOTIFY XSX9Z5XGWWYFKXTAYCES63B62527JKNX9XD0131Z08THVV8YW5BZG;
END IF;

IF in_decider_pub IS NOT NULL
THEN
  INSERT INTO aml_history
    (h_payto
    ,outcome_serial_id
    ,justification
    ,decider_pub
    ,decider_sig
    ,kyc_attributes_hash
    ,kyc_attributes_serial_id
    ) VALUES (
     in_h_normalized_payto
    ,my_outcome_serial_id
    ,in_justification
    ,in_decider_pub
    ,in_decider_sig
    ,in_kyc_attributes_hash
    ,my_kyc_attributes_serial_id
  );
END IF;

-- Trigger events
FOR i IN 1..COALESCE(array_length(ina_events,1),0)
LOOP
  ini_event = ina_events[i];
  INSERT INTO kyc_events
    (event_timestamp
    ,event_type
    ) VALUES (
     in_decision_time
    ,ini_event);
  IF (ini-event = 'ACCOUNT_OPEN')
  THEN
    UPDATE kyc_targets
       SET open_time=in_decision_time
          ,close_time=NULL
     WHERE h_normalized_payto=in_h_normalized_payto;
  END IF;
  IF (ini-event = 'ACCOUNT_IDLE')
  THEN
    UPDATE kyc_targets
       SET close_time=in_decision_time
     WHERE h_normalized_payto=in_h_normalized_payto;
  END IF;
END LOOP;

-- wake up taler-exchange-aggregator
INSERT INTO kyc_alerts
  (h_payto
  ,trigger_type
  ) VALUES (
   in_h_normalized_payto
  ,1
  )
 ON CONFLICT DO NOTHING;

EXECUTE FORMAT (
   'NOTIFY %s'
  ,in_notify_s);


END $$;


COMMENT ON FUNCTION exchange_do_insert_aml_decision(TEXT, BYTEA, BYTEA, INT8, INT8, JSONB, BYTEA, BYTEA, INT8, JSONB, BOOLEAN, TEXT, JSONB, TEXT, BYTEA, BYTEA, TEXT, TEXT[], TEXT)
  IS 'Checks whether the AML officer is eligible to make AML decisions and if so inserts the decision into the table';
