CREATE EXTENSION IF NOT EXISTS btree_gist;

CREATE DOMAIN password_hash AS bytea;
CREATE DOMAIN email_address_enc AS bytea;
CREATE DOMAIN email_address_hash AS bytea;
CREATE DOMAIN email_address AS text;

CREATE TYPE PERIOD AS RANGE (
  subtype = INSTANT
);

CREATE DOMAIN PERIOD_FROM AS PERIOD CHECK (NOT lower_inf(VALUE) AND lower_inc(VALUE) AND NOT upper_inc(VALUE));

CREATE TABLE email_addresses(
  email_address EMAIL_ADDRESS_ENC NOT NULL,
  _hash EMAIL_ADDRESS_HASH NOT NULL,
  created_at INSTANT NOT NULL,
  PRIMARY KEY (_hash)
);

-- Transaction-time state table for time-varying `users` fields.
CREATE TABLE users_history(
  user_id USER_ID NOT NULL,
  period PERIOD_FROM NOT NULL,
  -- `NULL`: not current, `TRUE`: current
  _is_current BOOLEAN NULL,
  updated_by USER_ID NOT NULL,
  display_name USER_DISPLAY_NAME NOT NULL,
  username USERNAME NULL,
  email EMAIL_ADDRESS_HASH NULL,
  password PASSWORD_HASH NULL,
  PRIMARY KEY (user_id, period),
  CHECK ((NOT upper_inf(period) AND _is_current IS NULL) OR (upper_inf(period) AND _is_current IS NOT NULL AND _is_current)),
  UNIQUE (user_id, _is_current),
  -- No overlapping rows for a given user
  EXCLUDE USING gist (user_id WITH =, period WITH &&),
  -- Sequential username uniqueness
  EXCLUDE USING gist (username WITH =, period WITH &&),
  EXCLUDE USING gist (email WITH =, period WITH &&),
  CONSTRAINT user_history_user_id__fk FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT user_history_updated_by__fk FOREIGN KEY (updated_by) REFERENCES users(user_id) ON DELETE RESTRICT ON UPDATE CASCADE,
  CONSTRAINT user_history_email__fk FOREIGN KEY (email) REFERENCES email_addresses(_hash) ON DELETE SET NULL ON UPDATE CASCADE
);

INSERT INTO users_history(user_id, period, _is_current, updated_by, display_name, username, email, password)
SELECT user_id, PERIOD(ctime, NULL), TRUE, user_id, display_name, username, NULL, password
FROM users;

ALTER TABLE users
  DROP COLUMN display_name,
  DROP COLUMN display_name_mtime,
  DROP COLUMN email_address,
  DROP COLUMN email_address_mtime,
  DROP COLUMN username,
  DROP COLUMN username_mtime,
  DROP COLUMN password,
  DROP COLUMN password_mtime;

ALTER TABLE users RENAME COLUMN ctime TO created_at;

ALTER TABLE users ALTER COLUMN user_id TYPE USER_ID;

ALTER TABLE users ADD COLUMN _is_current BOOLEAN NOT NULL DEFAULT TRUE;

ALTER TABLE users
  ADD CHECK (_is_current),
  ADD CONSTRAINT users_history__fk FOREIGN KEY (user_id, _is_current) REFERENCES users_history(user_id, _is_current) ON DELETE RESTRICT ON UPDATE NO ACTION DEFERRABLE INITIALLY DEFERRED;

CREATE VIEW users_current AS
  SELECT user_id, users.created_at, lower(period) AS updated_at, updated_by, is_administrator, display_name, username, email_addresses.email_address AS email, users_history.email AS _email_hash, password
  FROM users INNER JOIN users_history USING (user_id, _is_current) LEFT OUTER JOIN email_addresses ON users_history.email = email_addresses._hash
