-- OpenTelemetry `traceparent` span reference encoded as bytes.
-- The length is 26 bytes:
-- - 16 bytes for the u128 trace id
-- -  8 bytes for the u64 span id
-- -  1 byte for the flags
-- -  1 byte for the `is_remote` bool
-- todo: provide an impl using a composite type
create domain opentelemetry_trace_parent_bytes as bytea check (length(value) = 26);

alter table job
  add column producer_span opentelemetry_trace_parent_bytes null;

CREATE DOMAIN user_link_id AS UUID;
CREATE DOMAIN user_link_proof_id AS UUID;
CREATE DOMAIN outbound_email_id AS UUID;
CREATE DOMAIN outbound_email_request_id AS UUID;
CREATE DOMAIN idempotency_key AS UUID;
CREATE DOMAIN discord_user_id AS varchar(256);
CREATE DOMAIN gitlab_user_id AS varchar(256);
create domain digest_sha2_256 as bytea check (length(value) = 32);
create domain digest_sha3_256 as bytea check (length(value) = 32);
CREATE TYPE user_link_type AS ENUM ('Discord', 'Email', 'Gitlab');
CREATE TYPE user_link_visibility AS ENUM ('Private', 'Friend', 'Public');
CREATE TYPE email_payload_kind AS ENUM ('Marktwin', 'ResetPassword', 'VerifyEmail');
CREATE TYPE email_delivery_status AS ENUM ('Pending', 'Cancelled', 'Sent', 'Acknowledged');
CREATE TYPE email_request_status AS ENUM ('Pending', 'Cancelled', 'Timeout', 'Ok', 'Error');

CREATE TABLE gitlab_user (
  "gitlab_user_id" gitlab_user_id not null,
  primary key (gitlab_user_id)
);

CREATE TABLE discord_user (
  "discord_user_id" discord_user_id not null,
  primary key (discord_user_id)
);

CREATE TABLE user_link (
  "user_link_id" user_link_id not null,
  "period" period_lower not null,
  "user_id" user_id not null,
  "created_by" user_id not null,
  "deleted_by" user_id null,
  "user_link_type" user_link_type not null,
  "email_address_hash" email_address_hash null,
  "discord_user_id" discord_user_id null,
  "gitlab_user_id" gitlab_user_id null,
  primary key (user_link_id),
  constraint is_discord__ck check ((user_link_type = 'Discord') = (discord_user_id is not null)),
  constraint is_email__ck check ((user_link_type = 'Email') = (email_address_hash is not null)),
  constraint is_gitlab__ck check ((user_link_type = 'Gitlab') = (gitlab_user_id is not null)),
  constraint deleted_by__ck check ((deleted_by is not null) = (upper(period) is not null)),
  exclude using gist (period WITH &&, discord_user_id with =) where (user_link_type = 'Discord'),
  exclude using gist (period WITH &&, email_address_hash with =) where (user_link_type = 'Email'),
  exclude using gist (period WITH &&, gitlab_user_id with =) where (user_link_type = 'Gitlab'),
  constraint user_link__user__fk foreign key (user_id) references users(user_id) on delete restrict on update cascade,
  constraint user_link__created_by__fk foreign key (created_by) references users(user_id) on delete restrict on update cascade,
  constraint user_link__deleted_by__fk foreign key (deleted_by) references users(user_id) on delete restrict on update cascade,
  constraint user_link__email__fk foreign key (email_address_hash) references email_addresses(_hash) match simple on delete restrict on update cascade,
  constraint user_link__discord__fk foreign key (discord_user_id) references discord_user(discord_user_id) match simple on delete restrict on update cascade,
  constraint user_link__gitlab__fk foreign key (gitlab_user_id) references gitlab_user(gitlab_user_id) match simple on delete restrict on update cascade
);

-- rank for sorting
--
-- links are sorted by (rank, user_link_id)
CREATE TABLE user_link_rank_history (
  "user_link_id" user_link_id not null,
  "period" period_lower not null,
  "created_by" user_id not null,
  "deleted_by" user_id null,
  "rank" u16 not null,
  primary key (user_link_id, period),
  constraint deleted_by__ck check ((deleted_by is not null) = (upper(period) is not null)),
  constraint user_link_rank__created_by__fk foreign key (created_by) references users(user_id) on delete restrict on update cascade,
  constraint user_link_rank__deleted_by__fk foreign key (deleted_by) references users(user_id) on delete restrict on update cascade
);

-- link visibility
CREATE TABLE user_link_visibility_history (
  "user_link_id" user_link_id not null,
  "period" period_lower not null,
  "created_by" user_id not null,
  "deleted_by" user_id null,
  "visibility" user_link_visibility not null,
  primary key (user_link_id, period),
  constraint deleted_by__ck check ((deleted_by is not null) = (upper(period) is not null)),
  constraint user_link_visibility__created_by__fk foreign key (created_by) references users(user_id) on delete restrict on update cascade,
  constraint user_link_visibility__deleted_by__fk foreign key (deleted_by) references users(user_id) on delete restrict on update cascade
);

-- Outbound transactional email with a single recipient
--
-- rows indicate emails we want to send; it may not be delivered yet.
CREATE TABLE outbound_email (
  -- primary key for this email
  "outbound_email_id" outbound_email_id not null,
  -- key used for deduplication
  "idempotency_key" idempotency_key not null,
  -- user who sent the email
  "created_by" user_id not null,
  -- When this row was created (email ready for submission)
  "submitted_at" instant not null,
  -- If the email still not delivered after deadline, abort
  "deadline" instant not null,
  -- Usually `support@eternaltwin.org`
  "sender" email_address_hash not null,
  -- Recipient email
  "recipient" email_address_hash not null,
  -- payload kind / email template
  "payload_kind" email_payload_kind not null,
  -- email template version
  "payload_version" u8 not null,
  -- encrypted email payload
  "payload" bytea not null,
  "text_size" u16 not null,
  "text_sha2_256" digest_sha2_256 not null,
  "text_sha3_256" digest_sha3_256 not null,
  "html_size" u16 not null,
  "html_sha2_256" digest_sha2_256 not null,
  "html_sha3_256" digest_sha3_256 not null,
  -- overall delivery status across the different requests
  "delivery_status" email_delivery_status not null,
  -- when the next request submission attempt is scheduled
  "next_request_at" instant null,
  -- when the recipient read the email for the first time
  "read_at" instant null,
  primary key (outbound_email_id),
  constraint outboud_email__idempotency_key__uk unique (idempotency_key),
  constraint deadline_ck check (submitted_at < deadline),
  constraint next_request_at_ck check (submitted_at <= next_request_at and next_request_at < deadline),
  constraint outboud_email__created_by__fk foreign key (created_by) references users(user_id) on delete restrict on update cascade,
  constraint outboud_email__sender__fk foreign key (sender) references email_addresses(_hash) on delete restrict on update cascade,
  constraint outboud_email__recipient__fk foreign key (recipient) references email_addresses(_hash) on delete restrict on update cascade
);

-- -- email delivery priority. A higher value indicates higher priority.
-- CREATE TABLE outbound_email_priority_history (
--   "outbound_email_id" outbound_email_id not null,
--   "period" period_lower NOT NULL,
--   "priority" i8 not null default 0
-- );

-- Outbound email requests
--
-- Requests are handled as following:
-- 1. insert DB row in the table, commit
-- 2. send email request
-- 3. store result, commit
--
-- This ensures that an email is sent only if we add the row (and ensures there's no spam)
CREATE TABLE outbound_email_request (
  "outbound_email_request_id" outbound_email_request_id not null,
  -- email to send
  "outbound_email_id" outbound_email_id not null,
  -- the upper is always non-null
  -- if the state is pending, the request did not complete yet and it is a timeout
  -- else, it is the completion time
  "period" period_lower NOT NULL CHECK (not upper_inf(period)),
  "status" email_request_status not null,
  -- if `result` is not null, format version
  "result_version" u8 null,
  -- encrypted result if relevant
  "result" bytea null,
  primary key (outbound_email_request_id),
  exclude using gist (outbound_email_id with =, period with &&),
  constraint result_ck check (result is null OR (result is not null and result_version is not null)),
  constraint outbound_email_request__outbound_email__fk foreign key (outbound_email_id) references outbound_email(outbound_email_id) on delete restrict on update restrict
);

CREATE TABLE user_link_proof_manual (
  "user_link_proof_id" url NOT NULL,
  "user_link_id" user_id not null,
  "period" period_lower not null,
  "created_by" user_id not null,
  "deleted_by" user_id null
);

CREATE TABLE user_link_proof_email (
  "user_link_proof_id" url NOT NULL,
  "user_link_id" user_id not null,
  "period" period_lower not null,
  "created_by" user_id not null,
  "outbound_email_id" outbound_email_id not null,
  "deleted_by" user_id null
);
