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

-- @file merchant-0013.sql
-- @brief Creating trigger for the category change webhook
-- @author Bohdan Potuzhnyi
-- @author Vlada Svirsh


BEGIN;

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

SET search_path TO merchant;

-- Slug was incorrectly set to be globally unique, is only
-- unique per instance! Drop the constraint and add correct one.
ALTER TABLE merchant_token_families
  DROP CONSTRAINT merchant_token_families_slug_key,
  ADD UNIQUE (merchant_serial,slug),
  DROP COLUMN rounding, -- replaced by validity_granularity
  ADD validity_granularity INT8 NOT NULL CHECK
    (validity_granularity IN (60000000,3600000000,86400000000,604800000000,2592000000000,7776000000000,31536000000000))
    DEFAULT (2592000000000), -- 30 days
  ADD start_offset INT8 NOT NULL DEFAULT (0),
  ADD cipher_choice TEXT NOT NULL DEFAULT ('rsa(2048)'),
  ADD extra_data TEXT DEFAULT NULL;

COMMENT ON COLUMN merchant_token_families.validity_granularity
  IS 'To compute key lifetimes, we first round the payment deadline down to a multiple of this time; supported values are one minute, one hour, a day, a week, 30 days, 90 days or a year (indicatited using 365 days); adding the start_offset gets the start validity time; adding the duration to get the signature_valid_until value for the key';
COMMENT ON COLUMN merchant_token_families.start_offset
  IS 'This allows shifting the validity period of signatures to start a bit before the time rounded to the precision. For example, Swiss vignettes are valid for 14 months, from December of year X to January of year X+2. This can be achieve by setting a start_offset of 30 days, and a duration of 14 months and a precision of 1 year. The value given is in microseconds (but will be rounded to seconds).';
COMMENT ON COLUMN merchant_token_families.cipher_choice
  IS 'Specifies the type of cipher that should be used for this token family. Currently supported values are "cs" and "rsa($LEN)" where $LEN is the key length in bits.';
COMMENT ON COLUMN merchant_token_families.extra_data
  IS 'JSON field with family-specific meta data, such as the trusted_domains for subscriptions or expected_domains for discount tokens';


-- Keep proper periods for token keys.
ALTER TABLE merchant_token_family_keys
  DROP valid_before,
  DROP valid_after,
  ADD signature_validity_start INT8 NOT NULL DEFAULT (0),
  ADD signature_validity_end INT8 NOT NULL DEFAULT (0),
  ADD private_key_deleted_at INT8 NOT NULL DEFAULT (0),
  ADD private_key_created_at INT8 NOT NULL DEFAULT (0);

COMMENT ON COLUMN merchant_token_family_keys.signature_validity_start
  IS 'Specifies the earliest time at which tokens signed with this key can be considered valid. Allows tokens to be issued way in advance of their validity.';
COMMENT ON COLUMN merchant_token_family_keys.signature_validity_end
  IS 'Specifies when the tokens signed by this key expire.';
COMMENT ON COLUMN merchant_token_family_keys.private_key_deleted_at
  IS 'Specifies how long tokens signed by this key can be created, that is the point at which the private key may be deleted. Computed by determining when the *next* validity period starts, or when the overall token family validity period ends.';
COMMENT ON COLUMN merchant_token_family_keys.private_key_created_at
  IS 'Specifies when the private key was created. Not terribly useful, mostly for debugging.';

-- Function to replace placeholders in a string with a given value
CREATE OR REPLACE FUNCTION replace_placeholder(
  template TEXT,
  key TEXT,
  value TEXT
) RETURNS TEXT AS $$
BEGIN
  RETURN regexp_replace(
    template,
    '{{\s*' || key || '\s*}}', -- Match the key with optional spaces
    value,
    'g' -- Global replacement
  );
END;
$$ LANGUAGE plpgsql;

-- Trigger function to handle pending webhooks for category changes
CREATE OR REPLACE FUNCTION handle_category_changes()
RETURNS TRIGGER AS $$
DECLARE
  my_merchant_serial BIGINT;
  resolved_body TEXT;
  webhook RECORD; -- To iterate over all webhooks matching the event type
BEGIN
  -- Fetch the merchant_serial directly from the NEW or OLD row
  my_merchant_serial := COALESCE(OLD.merchant_serial, NEW.merchant_serial);

  -- INSERT case: Add a webhook for category addition
  IF TG_OP = 'INSERT' THEN
    FOR webhook IN
      SELECT webhook_serial,
             merchant_serial,
	     url,
	     http_method,
	     body_template
      FROM merchant_webhook
      WHERE event_type = 'category_added'
        AND merchant_serial = my_merchant_serial
    LOOP
      -- Resolve placeholders for the current webhook
      resolved_body := webhook.body_template;
      resolved_body := replace_placeholder(resolved_body,
                                           'webhook_type',
                                           'category_added');
      resolved_body := replace_placeholder(resolved_body,
                                           'category_serial',
                                           NEW.category_serial::TEXT);
      resolved_body := replace_placeholder(resolved_body,
                                           'category_name',
                                           NEW.category_name);
      resolved_body := replace_placeholder(resolved_body,
                                           'merchant_serial',
                                           my_merchant_serial::TEXT);

      -- Insert into pending webhooks for this webhook
      INSERT INTO merchant_pending_webhooks
      (merchant_serial, webhook_serial, url, http_method, body)
      VALUES
      (webhook.merchant_serial,
       webhook.webhook_serial,
       webhook.url,
       webhook.http_method,
       resolved_body);
    END LOOP;

    -- Notify the webhook service for the TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING
    NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG;
  END IF;

  -- UPDATE case: Add a webhook for category update
  IF TG_OP = 'UPDATE' THEN
    FOR webhook IN
      SELECT webhook_serial,
             merchant_serial,
	     url,
	     http_method,
	     body_template
      FROM merchant_webhook
      WHERE event_type = 'category_updated'
        AND merchant_serial = my_merchant_serial
    LOOP
      -- Resolve placeholders for the current webhook
      resolved_body := webhook.body_template;
      resolved_body := replace_placeholder(resolved_body,
                                           'webhook_type',
                                           'category_updated');
      resolved_body := replace_placeholder(resolved_body,
                                           'category_serial',
                                           NEW.category_serial::TEXT);
      resolved_body := replace_placeholder(resolved_body,
                                           'old_category_name',
                                           OLD.category_name);
      resolved_body := replace_placeholder(resolved_body,
                                           'category_name',
                                           NEW.category_name);
      resolved_body := replace_placeholder(resolved_body,
                                           'category_name_i18n',
                                           encode(NEW.category_name_i18n,
                                                  'escape'));
      resolved_body := replace_placeholder(resolved_body,
                                           'old_category_name_i18n',
                                           encode(OLD.category_name_i18n,
                                                  'escape'));

      -- Insert into pending webhooks for this webhook
      INSERT INTO merchant_pending_webhooks
      (merchant_serial, webhook_serial, url, http_method, body)
      VALUES
      (webhook.merchant_serial,
       webhook.webhook_serial,
       webhook.url,
       webhook.http_method,
       resolved_body);
    END LOOP;

    -- Notify the webhook service for the TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING
    NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG;
  END IF;

  -- DELETE case: Add a webhook for category deletion
  IF TG_OP = 'DELETE' THEN
    FOR webhook IN
      SELECT webhook_serial,
             merchant_serial,
	     url,
	     http_method,
	     body_template
      FROM merchant_webhook
      WHERE event_type = 'category_deleted'
        AND merchant_serial = my_merchant_serial
    LOOP
      -- Resolve placeholders for the current webhook
      resolved_body := webhook.body_template;
      resolved_body := replace_placeholder(resolved_body,
                                           'webhook_type',
                                           'category_deleted');
      resolved_body := replace_placeholder(resolved_body,
                                           'category_serial',
                                           OLD.category_serial::TEXT);
      resolved_body := replace_placeholder(resolved_body,
                                           'category_name',
                                           OLD.category_name);

      -- Insert into pending webhooks for this webhook
      INSERT INTO merchant_pending_webhooks
      (merchant_serial, webhook_serial, url, http_method, body)
      VALUES
      (webhook.merchant_serial,
       webhook.webhook_serial,
       webhook.url,
       webhook.http_method,
       resolved_body);
    END LOOP;

    -- Notify the webhook service for the TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING
    NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG;
  END IF;

  RETURN NULL;
END;
$$ LANGUAGE plpgsql;

-- Trigger to invoke the trigger function
CREATE TRIGGER trigger_category_changes
AFTER INSERT OR UPDATE OR DELETE
ON merchant_categories
FOR EACH ROW
EXECUTE FUNCTION handle_category_changes();

-- Function to handle inventory changes and notify webhooks
CREATE OR REPLACE FUNCTION handle_inventory_changes()
  RETURNS TRIGGER AS $$
DECLARE
  my_merchant_serial BIGINT;
  resolved_body TEXT;
  webhook RECORD; -- To iterate over all matching webhooks
BEGIN
  -- Fetch the merchant_serial directly from the NEW or OLD row
  my_merchant_serial := COALESCE(OLD.merchant_serial, NEW.merchant_serial);

  -- INSERT case: Notify webhooks for inventory addition
  IF TG_OP = 'INSERT' THEN
    FOR webhook IN
      SELECT webhook_serial,
             merchant_serial,
	     url,
	     http_method,
	     body_template
      FROM merchant_webhook
      WHERE event_type = 'inventory_added'
        AND merchant_serial = my_merchant_serial
      LOOP
        -- Resolve placeholders for the current webhook
        resolved_body := webhook.body_template;
        resolved_body := replace_placeholder(resolved_body,
                                             'webhook_type',
                                             'inventory_added');
        resolved_body := replace_placeholder(resolved_body,
                                             'product_serial',
                                             NEW.product_serial::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'product_id',
                                             NEW.product_id);
        resolved_body := replace_placeholder(resolved_body,
                                             'description',
                                             NEW.description);
        resolved_body := replace_placeholder(resolved_body,
                                             'description_i18n',
                                             encode(NEW.description_i18n,
                                                    'escape'));
        resolved_body := replace_placeholder(resolved_body,
                                             'unit',
                                             NEW.unit);
        resolved_body := replace_placeholder(resolved_body,
                                             'image',
                                             NEW.image);
        resolved_body := replace_placeholder(resolved_body,
                                             'taxes',
                                             encode(NEW.taxes,
                                                    'escape'));
        resolved_body := replace_placeholder(resolved_body,
                                             'price',
                                             NEW.price::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'total_stock',
                                             NEW.total_stock::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'total_sold',
                                             NEW.total_sold::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'total_lost',
                                             NEW.total_lost::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'address',
                                             encode(NEW.address,
                                                    'escape'));
        resolved_body := replace_placeholder(resolved_body,
                                             'next_restock',
                                             NEW.next_restock::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'minimum_age',
                                             NEW.minimum_age::TEXT);

        -- Insert into pending webhooks for this webhook
        INSERT INTO merchant_pending_webhooks
        (merchant_serial, webhook_serial, url, http_method, body)
        VALUES
          (webhook.merchant_serial,
           webhook.webhook_serial,
           webhook.url,
           webhook.http_method,
           resolved_body);
      END LOOP;

    -- Notify the webhook service
    NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG;
  END IF;

  -- UPDATE case: Notify webhooks for inventory update
  IF TG_OP = 'UPDATE' THEN
    FOR webhook IN
      SELECT webhook_serial,
             merchant_serial,
	     url,
	     http_method,
	     body_template
      FROM merchant_webhook
      WHERE event_type = 'inventory_updated'
        AND merchant_serial = my_merchant_serial
      LOOP
        -- Resolve placeholders for the current webhook
        resolved_body := webhook.body_template;
        resolved_body := replace_placeholder(resolved_body,
                                             'webhook_type',
                                             'inventory_updated');
        resolved_body := replace_placeholder(resolved_body,
                                             'product_serial',
                                             NEW.product_serial::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'product_id',
                                             NEW.product_id);
        resolved_body := replace_placeholder(resolved_body,
                                             'old_description',
                                             OLD.description);
        resolved_body := replace_placeholder(resolved_body,
                                             'description',
                                             NEW.description);
        resolved_body := replace_placeholder(resolved_body,
                                             'old_description_i18n',
                                             encode(OLD.description_i18n,
                                                    'escape'));
        resolved_body := replace_placeholder(resolved_body,
                                             'description_i18n',
                                             encode(NEW.description_i18n,
                                                    'escape'));
        resolved_body := replace_placeholder(resolved_body,
                                             'old_unit',
                                             OLD.unit);
        resolved_body := replace_placeholder(resolved_body,
                                             'unit',
                                             NEW.unit);
        resolved_body := replace_placeholder(resolved_body,
                                             'old_image',
                                             OLD.image);
        resolved_body := replace_placeholder(resolved_body,
                                             'image',
                                             NEW.image);
        resolved_body := replace_placeholder(resolved_body,
                                             'old_taxes',
                                             encode(OLD.taxes,
                                                    'escape'));
        resolved_body := replace_placeholder(resolved_body,
                                             'taxes',
                                             encode(NEW.taxes,
                                                    'escape'));
        resolved_body := replace_placeholder(resolved_body,
                                             'old_price',
                                             OLD.price::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'price',
                                             NEW.price::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'old_total_stock',
                                             OLD.total_stock::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'total_stock',
                                             NEW.total_stock::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'old_total_sold',
                                             OLD.total_sold::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'total_sold',
                                             NEW.total_sold::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'old_total_lost',
                                             OLD.total_lost::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'total_lost',
                                             NEW.total_lost::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'old_address',
                                             encode(OLD.address,
                                                    'escape'));
        resolved_body := replace_placeholder(resolved_body,
                                             'address',
                                             encode(NEW.address,
                                                    'escape'));
        resolved_body := replace_placeholder(resolved_body,
                                             'old_next_restock',
                                             OLD.next_restock::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'next_restock',
                                             NEW.next_restock::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'old_minimum_age',
                                             OLD.minimum_age::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'minimum_age',
                                             NEW.minimum_age::TEXT);

        -- Insert into pending webhooks for this webhook
        INSERT INTO merchant_pending_webhooks
        (merchant_serial, webhook_serial, url, http_method, body)
        VALUES
          (webhook.merchant_serial,
           webhook.webhook_serial,
           webhook.url,
           webhook.http_method,
           resolved_body);
      END LOOP;

    -- Notify the webhook service
    NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG;
  END IF;

  -- DELETE case: Notify webhooks for inventory deletion
  IF TG_OP = 'DELETE' THEN
    FOR webhook IN
      SELECT webhook_serial,
             merchant_serial,
	     url,
	     http_method,
	     body_template
      FROM merchant_webhook
      WHERE event_type = 'inventory_deleted'
        AND merchant_serial = my_merchant_serial
      LOOP
        -- Resolve placeholders for the current webhook
        resolved_body := webhook.body_template;
        resolved_body := replace_placeholder(resolved_body,
                                             'webhook_type',
                                             'inventory_deleted');
        resolved_body := replace_placeholder(resolved_body,
                                             'product_serial',
                                             OLD.product_serial::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'product_id',
                                             OLD.product_id);
        resolved_body := replace_placeholder(resolved_body,
                                             'description',
                                             OLD.description);
        resolved_body := replace_placeholder(resolved_body,
                                             'description_i18n',
                                             encode(OLD.description_i18n,
                                                    'escape'));
        resolved_body := replace_placeholder(resolved_body,
                                             'unit',
                                             OLD.unit);
        resolved_body := replace_placeholder(resolved_body,
                                             'image',
                                             OLD.image);
        resolved_body := replace_placeholder(resolved_body,
                                             'taxes',
                                             encode(OLD.taxes,
                                                    'escape'));
        resolved_body := replace_placeholder(resolved_body,
                                             'price',
                                             OLD.price::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'total_stock',
                                             OLD.total_stock::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'total_sold',
                                             OLD.total_sold::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'total_lost',
                                             OLD.total_lost::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'address',
                                             encode(OLD.address,
                                                    'escape'));
        resolved_body := replace_placeholder(resolved_body,
                                             'next_restock',
                                             OLD.next_restock::TEXT);
        resolved_body := replace_placeholder(resolved_body,
                                             'minimum_age',
                                             OLD.minimum_age::TEXT);

        -- Insert into pending webhooks for this webhook
        INSERT INTO merchant_pending_webhooks
        (merchant_serial, webhook_serial, url, http_method, body)
        VALUES
          (webhook.merchant_serial,
           webhook.webhook_serial,
           webhook.url,
           webhook.http_method,
           resolved_body);
      END LOOP;

    -- Notify the webhook service
    NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG;
  END IF;

  RETURN NULL;
END;
$$ LANGUAGE plpgsql;

-- Trigger to invoke the trigger function
CREATE TRIGGER trigger_inventory_changes
  AFTER INSERT OR UPDATE OR DELETE
  ON merchant_inventory
  FOR EACH ROW
EXECUTE FUNCTION handle_inventory_changes();


COMMIT;
