CREATE DOMAIN hammerfest_server AS VARCHAR(13) CHECK (value IN ('hammerfest.es', 'hammerfest.fr', 'hfest.net'));
CREATE DOMAIN hammerfest_session_key AS VARCHAR(26) CHECK (value ~ '^[0-9a-z]{26}$');
CREATE DOMAIN hammerfest_user_id AS VARCHAR(10) CHECK (value ~ '^[1-9]\d{0,9}$');
CREATE DOMAIN twinoid_user_id AS VARCHAR(10) CHECK (value ~ '^[1-9]\d{0,9}$');
-- Represents a point in time, with millisecond precision
CREATE DOMAIN instant AS TIMESTAMP(3) WITH TIME ZONE;

CREATE TABLE public.twinoid_users (
  -- User ID on the Twinoid server
  twinoid_user_id TWINOID_USER_ID PRIMARY KEY NOT NULL,
  -- Twinoid name
  name VARCHAR(50) NOT NULL
);

-- Active links between Eternaltwin and Twinoid users
CREATE TABLE public.twinoid_user_links (
  -- Eternaltwin user id
  user_id UUID NOT NULL,
  -- User ID on the Twinoid server
  twinoid_user_id TWINOID_USER_ID NOT NULL,
  -- Link creation time
  ctime INSTANT NOT NULL,
  PRIMARY KEY (user_id, twinoid_user_id),
  CONSTRAINT twinoid_user_link__user__fk FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE RESTRICT ON UPDATE CASCADE,
  CONSTRAINT twinoid_user_link__twinoid_user__fk FOREIGN KEY (twinoid_user_id) REFERENCES twinoid_users(twinoid_user_id) ON DELETE RESTRICT ON UPDATE CASCADE
);

-- Cancelled links between Eternaltwin and Twinoid users
CREATE TABLE public.old_twinoid_user_links (
  -- Eternaltwin user id
  user_id UUID NOT NULL,
  -- Twinoid user id
  twinoid_user_id TWINOID_USER_ID NOT NULL,
  start_time INSTANT,
  end_time INSTANT,
  PRIMARY KEY (user_id, twinoid_user_id),
  CONSTRAINT twinoid_user_link__user__fk FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE RESTRICT ON UPDATE CASCADE,
  CONSTRAINT twinoid_user_link__twinoid_user__fk FOREIGN KEY (twinoid_user_id) REFERENCES twinoid_users(twinoid_user_id) ON DELETE RESTRICT ON UPDATE CASCADE
);

ALTER TABLE hammerfest_user_links
  RENAME hammerfest_user_id TO old_hammerfest_user_id;
ALTER TABLE hammerfest_user_links
  ADD COLUMN hammerfest_user_id HAMMERFEST_USER_ID;
ALTER TABLE hammerfest_user_links
  ALTER COLUMN hammerfest_server TYPE HAMMERFEST_SERVER;
-- noinspection SqlWithoutWhere
UPDATE hammerfest_user_links
SET hammerfest_user_id = old_hammerfest_user_id::VARCHAR;

ALTER TABLE hammerfest_users
  ADD COLUMN hammerfest_user_id HAMMERFEST_USER_ID;
ALTER TABLE hammerfest_users
  RENAME "server" TO hammerfest_server;
ALTER TABLE hammerfest_users
  ALTER COLUMN hammerfest_server TYPE HAMMERFEST_SERVER;
-- noinspection SqlWithoutWhere
UPDATE hammerfest_users
SET hammerfest_user_id = user_id::VARCHAR;

ALTER TABLE hammerfest_user_links
  DROP CONSTRAINT hammerfest_user_link__hammerfest_user__fk;
ALTER TABLE hammerfest_users
  DROP CONSTRAINT hammerfest_users_pkey;
ALTER TABLE hammerfest_users
  ADD PRIMARY KEY (hammerfest_server, hammerfest_user_id);
ALTER TABLE hammerfest_user_links
  ADD CONSTRAINT hammerfest_user_link__hammerfest_user__fk FOREIGN KEY (hammerfest_server, hammerfest_user_id) REFERENCES hammerfest_users(hammerfest_server, hammerfest_user_id) ON DELETE RESTRICT ON UPDATE CASCADE;

ALTER TABLE hammerfest_servers
  RENAME domain TO hammerfest_server;
ALTER TABLE hammerfest_servers
  ALTER COLUMN hammerfest_server TYPE HAMMERFEST_SERVER;

ALTER TABLE hammerfest_users
  DROP COLUMN user_id;
ALTER TABLE hammerfest_user_links
  DROP COLUMN old_hammerfest_user_id;

-- Active Hammerfest sessions
CREATE TABLE public.hammerfest_sessions (
  hammerfest_server HAMMERFEST_SERVER NOT NULL,
  hammerfest_session_key BYTEA NOT NULL,
  _hammerfest_session_key_hash BYTEA NOT NULL,
  hammerfest_user_id HAMMERFEST_USER_ID NOT NULL,
  -- Session creation time
  ctime INSTANT NOT NULL,
  -- Session access time
  atime INSTANT NOT NULL,
  CHECK (atime >= ctime),
  PRIMARY KEY (hammerfest_server, _hammerfest_session_key_hash),
  UNIQUE (hammerfest_server, hammerfest_user_id),
  CONSTRAINT hammerfest_session__hammerfest_user__fk FOREIGN KEY (hammerfest_server, hammerfest_user_id) REFERENCES hammerfest_users(hammerfest_server, hammerfest_user_id) ON DELETE CASCADE ON UPDATE CASCADE
);

-- Revoked Hammerfest sessions
CREATE TABLE public.old_hammerfest_sessions (
  hammerfest_server HAMMERFEST_SERVER NOT NULL,
  hammerfest_session_key BYTEA NOT NULL,
  _hammerfest_session_key_hash BYTEA NOT NULL,
  hammerfest_user_id HAMMERFEST_USER_ID NOT NULL,
  -- Session creation time
  ctime INSTANT NOT NULL,
  -- Session access time
  atime INSTANT NOT NULL,
  -- Session deletion time
  dtime INSTANT NOT NULL,
  CHECK (atime >= ctime),
  CHECK (dtime >= atime),
  CHECK (dtime > ctime),
  PRIMARY KEY (hammerfest_server, _hammerfest_session_key_hash, ctime),
  CONSTRAINT old_hammerfest_session__hammerfest_user__fk FOREIGN KEY (hammerfest_server, hammerfest_user_id) REFERENCES hammerfest_users(hammerfest_server, hammerfest_user_id) ON DELETE CASCADE ON UPDATE CASCADE
);

-- Active Twinoid access tokens
CREATE TABLE public.twinoid_access_tokens (
  twinoid_access_token BYTEA NOT NULL,
  _twinoid_access_token_hash BYTEA NOT NULL,
  twinoid_user_id TWINOID_USER_ID NOT NULL,
  -- Access token creation time
  ctime INSTANT NOT NULL,
  -- Access token access time
  atime INSTANT NOT NULL,
  -- Access token expiration time
  expiration_time INSTANT NOT NULL,
  CHECK (ctime <= atime),
  CHECK (atime <= expiration_time),
  CHECK (ctime < expiration_time),
  PRIMARY KEY (_twinoid_access_token_hash),
  UNIQUE (twinoid_user_id),
  CONSTRAINT twinoid_access_token__twinoid_user__fk FOREIGN KEY (twinoid_user_id) REFERENCES twinoid_users(twinoid_user_id) ON DELETE CASCADE ON UPDATE CASCADE
);

-- Revoked or expired Twinoid access tokens
CREATE TABLE public.old_twinoid_access_tokens (
  twinoid_access_token BYTEA NOT NULL,
  _twinoid_access_token_hash BYTEA NOT NULL,
  twinoid_user_id TWINOID_USER_ID NOT NULL,
  -- Access token creation time
  ctime INSTANT NOT NULL,
  -- Access token access time
  atime INSTANT NOT NULL,
  -- Refresh token deletion time
  dtime INSTANT NOT NULL,
  -- Access token expiration time
  expiration_time INSTANT NOT NULL,
  CHECK (ctime <= atime),
  CHECK (atime <= expiration_time),
  CHECK (ctime < expiration_time),
  CHECK (atime <= dtime),
  CHECK (ctime < dtime),
  PRIMARY KEY (_twinoid_access_token_hash, ctime),
  CONSTRAINT twinoid_access_token__twinoid_user__fk FOREIGN KEY (twinoid_user_id) REFERENCES twinoid_users(twinoid_user_id) ON DELETE CASCADE ON UPDATE CASCADE
);

-- Active Twinoid refresh tokens
CREATE TABLE public.twinoid_refresh_tokens (
  twinoid_refresh_token BYTEA NOT NULL,
  _twinoid_refresh_token_hash BYTEA NOT NULL,
  twinoid_user_id TWINOID_USER_ID NOT NULL,
  -- Refresh token creation time
  ctime INSTANT NOT NULL,
  -- Refresh token access time
  atime INSTANT NOT NULL,
  CHECK (ctime <= atime),
  PRIMARY KEY (_twinoid_refresh_token_hash),
  UNIQUE (twinoid_user_id),
  CONSTRAINT twinoid_refresh_token__twinoid_user__fk FOREIGN KEY (twinoid_user_id) REFERENCES twinoid_users(twinoid_user_id) ON DELETE CASCADE ON UPDATE CASCADE
);

-- Revoked Twinoid refresh tokens
CREATE TABLE public.old_twinoid_refresh_tokens (
  twinoid_refresh_token BYTEA NOT NULL,
  _twinoid_refresh_token_hash BYTEA NOT NULL,
  twinoid_user_id TWINOID_USER_ID NOT NULL,
  -- Refresh token creation time
  ctime INSTANT NOT NULL,
  -- Refresh token access time
  atime INSTANT NOT NULL,
  -- Refresh token deletion time
  dtime INSTANT NOT NULL,
  CHECK (ctime <= atime),
  CHECK (atime <= dtime),
  CHECK (ctime < dtime),
  PRIMARY KEY (_twinoid_refresh_token_hash, ctime),
  CONSTRAINT old_twinoid_refresh_token__twinoid_user__fk FOREIGN KEY (twinoid_user_id) REFERENCES twinoid_users(twinoid_user_id) ON DELETE CASCADE ON UPDATE CASCADE
);

ALTER TABLE users
  ALTER COLUMN ctime TYPE INSTANT,
  ALTER COLUMN display_name_mtime TYPE INSTANT,
  ALTER COLUMN email_address_mtime TYPE INSTANT,
  ALTER COLUMN username_mtime TYPE INSTANT,
  ALTER COLUMN password_mtime TYPE INSTANT;

ALTER TABLE email_verifications
  ALTER COLUMN ctime TYPE INSTANT,
  ALTER COLUMN validation_time TYPE INSTANT;

ALTER TABLE sessions
  ALTER COLUMN ctime TYPE INSTANT,
  ALTER COLUMN atime TYPE INSTANT;

ALTER TABLE oauth_clients
  ALTER COLUMN ctime TYPE INSTANT,
  ALTER COLUMN display_name_mtime TYPE INSTANT,
  ALTER COLUMN app_uri_mtime TYPE INSTANT,
  ALTER COLUMN callback_uri_mtime TYPE INSTANT,
  ALTER COLUMN secret_mtime TYPE INSTANT;

ALTER TABLE old_oauth_client_display_names
  ALTER COLUMN start_time TYPE INSTANT;

ALTER TABLE old_oauth_client_app_uris
  ALTER COLUMN start_time TYPE INSTANT;

ALTER TABLE old_oauth_client_callback_uris
  ALTER COLUMN start_time TYPE INSTANT;

ALTER TABLE old_oauth_client_secrets
  ALTER COLUMN start_time TYPE INSTANT;

ALTER TABLE oauth_access_tokens
  ALTER COLUMN ctime TYPE INSTANT,
  ALTER COLUMN atime TYPE INSTANT;

ALTER TABLE hammerfest_user_links
  ALTER COLUMN ctime TYPE INSTANT;

ALTER TABLE forum_sections
  ALTER COLUMN ctime TYPE INSTANT,
  ALTER COLUMN display_name_mtime TYPE INSTANT,
  ALTER COLUMN locale_mtime TYPE INSTANT;

ALTER TABLE forum_threads
  ALTER COLUMN ctime TYPE INSTANT,
  ALTER COLUMN title_mtime TYPE INSTANT,
  ALTER COLUMN is_pinned_mtime TYPE INSTANT,
  ALTER COLUMN is_locked_mtime TYPE INSTANT;

ALTER TABLE forum_posts
  ALTER COLUMN ctime TYPE INSTANT;

ALTER TABLE forum_post_revisions
  ALTER COLUMN time TYPE INSTANT;

ALTER TABLE forum_role_grants
  ALTER COLUMN start_time TYPE INSTANT;

ALTER TABLE forum_role_revocations
  ALTER COLUMN start_time TYPE INSTANT,
  ALTER COLUMN end_time TYPE INSTANT;
