create type sos."delivery_type_enum"      as enum ('hand_shipped', 'easypost', 'hand_delivered');
create type sos."transaction_state_enum"  as enum ('started', 'completed', 'cancelled', 'expired');
create type sos."payment_type_enum"       as enum ('ks_reward', 'stripe', 'paypal', 'account_credit');
create type sos."stockchange_type_enum"   as enum ('purchase', 'shipment', 'admin');
create type sos."stock_change_dir_enum"   as enum ('added', 'subtracted');

create table sos."user" (
  user_uuid uuid primary key default uuid_generate_v4(),
  user_email citext unique not null,
  user_email_confirmed boolean not null default false,
  user_password_hash varchar(60),
  user_time_password_changed timestamptz not null default now(),
  user_time_registered timestamptz not null default now(),
  user_time_email_confirmed timestamptz,
  user_is_admin bool not null default false
);

create table sos."email_link" (
  email_link_uuid uuid primary key default uuid_generate_v4(),
  email_link_user_uuid uuid not null references sos."user" (user_uuid),
  email_link_time_created timestamptz not null default now(),
  email_link_timeout_length interval not null,
  email_link_login_hash varchar(60) not null,
  email_link_time_used timestamptz
);

create table sos."cart" (
  cart_uuid uuid primary key default uuid_generate_v4()
);

create table sos."session" (
  session_uuid uuid primary key default uuid_generate_v4(),
  session_time_created timestamptz not null default now(),
  session_time_last_active timestamptz not null default now(),
  session_time_logged_out timestamptz null,
  session_timeout_length interval not null,
  session_ip_address varchar(50)  not null,
  session_user_agent varchar(500) not null,
  session_referer    varchar(500) not null,
  session_user_uuid uuid references sos."user" (user_uuid),
  session_cart uuid references sos."cart" (cart_uuid)
);

create table sos."item" (
  item_uuid uuid primary key default uuid_generate_v4(),
  item_name text not null,
  item_description text not null,
  item_urlslug citext unique not null,
  item_price_cents integer not null,
  item_published  boolean not null default true
);

create table sos."cart_item" (
  cart_item_uuid uuid primary key default uuid_generate_v4(),
  cart_item_item_uuid uuid not null references sos."item" (item_uuid),
  cart_item_cart_uuid uuid not null references sos."cart" (cart_uuid),
  cart_item_count integer not null default 1 check (cart_item_count > 0),
  cart_item_time_added timestamptz not null default now()
);

create table sos."image" (
  image_uuid uuid primary key default uuid_generate_v4(),
  image_featured boolean not null default false,
  image_item_uuid uuid not null references sos."item" (item_uuid),
  image_large_file bytea not null,
  image_thumb_file bytea not null,
  image_mime_type varchar not null,
  image_uploader_uuid uuid not null references sos."user" (user_uuid),
  image_date_uploaded timestamptz not null default now()
);

create unique index only_one_primary_key on sos."image" (image_item_uuid, image_featured)
  where image_featured = true;

create table sos."category" (
  category_uuid uuid primary key default uuid_generate_v4(),
  category_name text not null,
  category_description text not null,
  category_urlslug citext unique not null
);

create table sos."category_item" (
  category_item_uuid uuid primary key default uuid_generate_v4(),
  category_item_item_uuid uuid not null references sos."item" (item_uuid),
  category_item_category_uuid uuid not null references sos."category" (category_uuid)
);

create table sos."category_category" (
  category_category_uuid uuid primary key default uuid_generate_v4(),
  category_category_parent_uuid uuid not null references sos."category" (category_uuid),
  category_category_child_uuid uuid not null references sos."category" (category_uuid)
);

create table sos."address" (
  address_uuid  uuid  primary key default uuid_generate_v4(),
  address_name  text  not null,
  address_company text,
  address_street1 text not null,
  address_street2 text,
  address_city  text not null,
  address_state text not null,
  address_zip   text not null,
  address_country text not null,
  address_phone text,
  address_easypost_id  text
);

create table sos."delivery" (
  delivery_uuid   uuid   primary key default uuid_generate_v4(),
  delivery_type   sos.delivery_type_enum  not null,
  unique (delivery_uuid, delivery_type)
);

create table sos."delivery_hand_shipped" (
  delivery_uuid             uuid          not null,
  delivery_type             sos.delivery_type_enum  check (delivery_type = 'hand_shipped'),
  foreign key (delivery_uuid, delivery_type) references sos."delivery" (delivery_uuid, delivery_type),

  delivery_price_cents      integer       not null,
  delivery_tracking_number  text          not null,
  delivery_date_shipped     timestamptz   not null default now()
);

create table sos."delivery_easypost" (
  delivery_uuid             uuid          not null,
  delivery_type             sos.delivery_type_enum  check (delivery_type = 'easypost'),
  foreign key (delivery_uuid, delivery_type) references sos."delivery" (delivery_uuid, delivery_type),

  delivery_price_cents      integer       not null,
  delivery_tracking_number  text          not null,
  delivery_date_shipped     timestamptz   not null,
  delivery_easypost_id      text          not null
);

create table sos."delivery_hand_delivered" (
  delivery_uuid             uuid          not null,
  delivery_type             sos.delivery_type_enum  check (delivery_type = 'hand_delivered'),
  foreign key (delivery_uuid, delivery_type) references sos."delivery" (delivery_uuid, delivery_type),

  delivery_description      text          not null,
  delivery_date_delivered   timestamptz   not null
);

create table sos."order" (
  order_uuid            uuid            primary key default uuid_generate_v4(),
  order_number          serial          not null unique,
  order_start_time      timestamptz     not null default now(),
  order_user_uuid       uuid            references sos."user" (user_uuid),
  order_address_uuid    uuid            references sos."address" (address_uuid),
  order_delivery_uuid   uuid            references sos."delivery" (delivery_uuid)
);

create table sos."coupon" (
  coupon_uuid                     uuid            primary key default uuid_generate_v4(),
  coupon_code                     varchar(50)     unique not null,
  coupon_valid_until              timestamptz     not null,
  coupon_free_shipping            boolean         not null default false,
  coupon_number_allowed_uses      integer         not null default 1 check (coupon_number_allowed_uses > 0),
  coupon_flat_discount_cents      integer         not null default 0 check (coupon_flat_discount_cents >= 0),
  coupon_percent_discount         integer         not null default 0 check (coupon_percent_discount >= 0 and coupon_percent_discount <= 100),
  coupon_per_sock_discount_cents  integer         not null default 0 check (coupon_per_sock_discount_cents >= 0),
  coupon_number_of_socks_free     integer         not null default 0 check (coupon_number_of_socks_free >= 0),

  constraint only_one_coupon_discount check (
    (
      case when coupon_flat_discount_cents     = 0 then 0 else 1 end
    + case when coupon_percent_discount        = 0 then 0 else 1 end
    + case when coupon_per_sock_discount_cents = 0 then 0 else 1 end
    + case when coupon_number_of_socks_free    = 0 then 0 else 1 end
    ) < 2
  )
);

create table sos."transaction" (
  transaction_uuid                        uuid                      primary key default uuid_generate_v4(),
  transaction_order_uuid                  uuid                      references sos."order" (order_uuid),
  transaction_session_uuid                uuid                      not null references sos."session" (session_uuid),
  transaction_cart_uuid                   uuid                      references sos."cart" (cart_uuid),
  transaction_coupon_uuid                 uuid                      references sos."coupon" (coupon_uuid),
  transaction_start_time                  timestamptz               not null default now(),
  transaction_completion_time             timestamptz,
  transaction_payment_state               sos.transaction_state_enum  not null default 'started',
  transaction_item_total_price            integer                   not null,
  transaction_coupon_effective_discount   integer                   not null default 0,
  transaction_shipping_price              integer,
  transaction_tax_price                   integer,

  constraint transaction_cannot_complete_without_shipping check (transaction_payment_state != 'completed' or transaction_shipping_price is not null),
  constraint transaction_no_discount_without_coupon check (transaction_coupon_effective_discount = 0 or transaction_coupon_uuid is not null)
  -- TODO: Partial index on cart_uuid to disallow two "started" transactions with the same cart
  -- TODO: Partial index on order_uuid to disallow two "started" transactions with the same order
);

create table sos."payment" (
  payment_uuid              uuid                  primary key default uuid_generate_v4(),
  payment_type              sos.payment_type_enum     not null,
  payment_time              timestamptz           not null default now(),
  payment_value_cents       integer               not null,
  payment_transaction_uuid  uuid                  references sos."transaction" (transaction_uuid),

  unique (payment_uuid, payment_type)
);

create table sos."payment_ks_reward" (
  payment_uuid           uuid                  primary key default uuid_generate_v4(),
  payment_type           sos.payment_type_enum     check (payment_type = 'ks_reward'),
  foreign key (payment_uuid, payment_type) references sos."payment" (payment_uuid, payment_type)

  -- Kickstarter data?
  -- TODO: Figure out how this should work
);

create table sos."payment_stripe" (
  payment_uuid            uuid                  primary key default uuid_generate_v4(),
  payment_type            sos.payment_type_enum     check (payment_type = 'stripe'),
  foreign key (payment_uuid, payment_type) references sos."payment" (payment_uuid, payment_type),

  stripe_payment_intent_id   text                  unique not null,
  stripe_receipt_email       citext                not null,
  stripe_receipt_number      text                  NULL
);

create table sos."shipment" (
  shipment_uuid          uuid          primary key default uuid_generate_v4(),
  shipment_date          timestamptz   not null default now(),
  shipment_description   text          not null
);

create table sos."admin_withdrawal" (
  withdrawal_uuid         uuid          primary key default uuid_generate_v4(),
  withdrawal_date         timestamptz   not null default now(),
  withdrawal_description  text          not null
);

create table sos."item_stockchange" (
  stockchange_uuid        uuid                    primary key default uuid_generate_v4(),
  stockchange_type        sos.stockchange_type_enum   not null,
  unique (stockchange_uuid, stockchange_type),

  stockchange_item_uuid   uuid                    references sos."item" (item_uuid),
  stockchange_change      integer                 not null check (stockchange_change > 0),
  stockchange_direction   sos.stock_change_dir_enum   not null
);

create table sos."item_stockchange_purchase" (
  stockchange_uuid            uuid                    primary key default uuid_generate_v4(),
  stockchange_type            sos.stockchange_type_enum   check (stockchange_type = 'purchase'),
  foreign key (stockchange_uuid, stockchange_type) references sos."item_stockchange" (stockchange_uuid, stockchange_type),

  stockchange_transaction_uuid   uuid                    references sos."transaction" (transaction_uuid)
);

create table sos."item_stockchange_shipment" (
  stockchange_uuid          uuid                    primary key default uuid_generate_v4(),
  stockchange_type          sos.stockchange_type_enum   check (stockchange_type = 'shipment'),
  foreign key (stockchange_uuid, stockchange_type) references sos."item_stockchange" (stockchange_uuid, stockchange_type),

  stockchange_shipment_uuid  uuid                    references sos."shipment" (shipment_uuid)
);

create table sos."item_stockchange_admin" (
  stockchange_uuid        uuid                    primary key default uuid_generate_v4(),
  stockchange_type        sos.stockchange_type_enum   check (stockchange_type = 'admin'),
  foreign key (stockchange_uuid, stockchange_type) references sos."item_stockchange" (stockchange_uuid, stockchange_type),

  stockchange_withdrawal_uuid  uuid                    references sos."admin_withdrawal" (withdrawal_uuid)
);

create table sos."config" (
  config_uuid                     uuid                primary key default uuid_generate_v4(),
  config_date_updated             timestamptz         not null default now(),
  config_updated_by               uuid                references sos."user" (user_uuid),
  config_default_tax_percent      numeric(8,6)        not null default 7.250
);

insert into sos."config" default values;
