/*
  Вспомогательные функции

* FUNCTION soa_upd(a_old TEXT) RETURNS TEXT
* FUNCTION domain_id(a_name TEXT, a_type TEXT DEFAULT 'NATIVE') RETURNS INTEGER
* PROCEDURE acme_insert(a_domain_id INT, a_name TEXT, a_ip TEXT, a_ttl INT)

*/

CREATE OR REPLACE FUNCTION soa_upd(a_old TEXT) RETURNS TEXT AS $_$
/*
    принять SOA, проверить на "сегодня", если сегодня, то во вторую пару цифр сделать +1
    если не сегодня, или null, то сегодня += 00
    если SOA в принципе не по дате, к ней сделать +1
*/
DECLARE
  v_soa TEXT;
  v_id  INTEGER;
BEGIN
  v_soa := to_char(current_timestamp, 'YYYYMMDD');
  IF a_old IS NULL OR a_old < v_soa || '00' THEN
    -- SOA нет или меньше сегодняшнего
    RETURN v_soa || '00';
  END IF;
  v_id := substr(a_old, length(v_soa) + 1)::INT + 1;
  v_soa := substr(a_old, 1, length(v_soa)) || CASE WHEN v_id < 10 THEN '0' ELSE  '' END || v_id::TEXT;
  RETURN v_soa;
END
$_$ LANGUAGE plpgsql;

/*
SELECT x, soa_upd(x) FROM unnest(ARRAY[
  null
, '2023060100'
, '2023060600'
, '2023060604'
, '2023060624'
, '2024060624'
, '202406061100'
]) x;
*/

-- result was changed to bigint
-- DROP FUNCTION IF EXISTS domain_id(text,text);

CREATE OR REPLACE FUNCTION domain_id(a_name TEXT, a_type TEXT DEFAULT 'NATIVE') RETURNS BIGINT AS $_$
/*
  Вернуть ID домена, создав его при необходимости
*/
DECLARE
  v_id  INTEGER;
BEGIN
  SELECT INTO v_id id FROM domains WHERE name = a_name;
  IF NOT FOUND THEN
    INSERT INTO domains (name, type) VALUES
      (a_name, a_type)
      RETURNING id INTO v_id
    ;
  END IF;
  RETURN v_id;
END
$_$ LANGUAGE plpgsql;

CREATE OR REPLACE PROCEDURE acme_insert(a_domain_id BIGINT, a_name TEXT, a_ip TEXT, a_ttl INT) AS $_$
/*
  Добавление в зону для заданного a_ip записей для передачи ему контроля над зоной a_name.
  Это используется в DNS-01 challenge ACME
*/
BEGIN
  WITH acme(name, type, content) AS (VALUES
    (                      a_name, 'A',     a_ip)
  , ('*.'               || a_name, 'A',     a_ip)
  , ('acme-'            || a_name, 'NS',    'ns.'   || a_name)
  , ('_acme-challenge.' || a_name, 'CNAME', 'acme-' || a_name)
  )
  INSERT INTO records (domain_id, name, ttl, type, prio, content)
    SELECT a_domain_id, name, a_ttl, type, 0, content
      FROM acme
  ;
END;
$_$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION soa_mk(
  a_domain_id BIGINT
, a_ns_admin TEXT
, a_refresh  INTEGER DEFAULT  10800 -- 3 hours
, a_retry    INTEGER DEFAULT   3600 -- 1 hour
, a_expire   INTEGER DEFAULT 604800 -- 7 days
, a_ttl      INTEGER DEFAULT   1800 -- 30 min
) RETURNS TEXT AS $_$
/*
  Получить новый серийный номер зоны, проверить наличие NSERVER и вернуть строку SOA

  refresh -- time lag until the slave again asks the master for a current version of the zone file
  retry   -- Should this request go unanswered, the “Retry” field regulates when a new attempt is to be carried out (< refresh)
  expire  -- determines how long the zone file may still be used before the server refuses DNS information delivery
  ttl     -- how long a client may hold the requested information in the cache before a new request must be sent

  Each value in seconds
*/
DECLARE
  v_ns        text := current_setting('vars.ns');   -- master DNS host
  v_stamp     text;                       -- zone SOA timestamp
  v_stamp_old text;                       -- previous zone SOA timestamp
BEGIN
  -- check NSERVER
  IF coalesce(v_ns,'') = '' THEN
    RAISE EXCEPTION 'NSERVER is not set';
  END IF;
  -- calculate SOA with next serial
  SELECT INTO v_stamp_old split_part(content, ' ', 3) FROM records WHERE domain_id = a_domain_id AND type = 'SOA';
  v_stamp := soa_upd(v_stamp_old);

  RETURN concat_ws(' ', v_ns, a_ns_admin, v_stamp, a_refresh, a_retry, a_expire, a_ttl);
END;
$_$ LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION csum_exists(a_domain_id BIGINT) RETURNS BOOL AS $_$
/*
  Проверить совпадение vars.csum с загруженным в БД прошлый раз.
  Если нет таблицы dcape_csum - создать ее
  Если csum отличается - обновить
*/
DECLARE
  v_csum     text := current_setting('vars.csum');   -- file csum
  v_csum_old text;
BEGIN
  RAISE NOTICE 'Source csum: %', v_csum;
  IF to_regclass('public.dcape_csum') IS NULL THEN
    CREATE TABLE dcape_csum(
      domain_id bigint primary key REFERENCES domains(id) ON DELETE CASCADE
    , csum text
    , updated_at timestamptz(0)
    );
  ELSE
    SELECT INTO v_csum_old csum FROM dcape_csum WHERE domain_id = a_domain_id;
  END IF;
  IF v_csum_old IS NULL THEN
    INSERT INTO dcape_csum(domain_id, csum, updated_at) VALUES (a_domain_id, v_csum, now());
  ELSIF v_csum_old <> v_csum THEN
    UPDATE dcape_csum SET csum = v_csum, updated_at = now() WHERE domain_id = a_domain_id;
  ELSE
    RETURN TRUE;
  END IF;
  RETURN FALSE;
END;
$_$ LANGUAGE plpgsql;
