-- +goose Up
-- accounts holds the editable source-of-truth user-account state.
-- email and user_name remain UNIQUE for both live and soft-deleted records:
-- emails are never reassigned to a fresh user_id after DeleteUser, and
-- user_name is immutable for the lifetime of the account.
CREATE TABLE accounts (
    user_id            text PRIMARY KEY,
    email              text NOT NULL,
    user_name          text NOT NULL,
    display_name       text NOT NULL DEFAULT '',
    preferred_language text NOT NULL,
    time_zone          text NOT NULL,
    declared_country   text,
    created_at         timestamptz NOT NULL,
    updated_at         timestamptz NOT NULL,
    deleted_at         timestamptz,
    CONSTRAINT accounts_email_unique     UNIQUE (email),
    CONSTRAINT accounts_user_name_unique UNIQUE (user_name)
);

-- Newest-first listing index used by the trusted admin user-list surface.
CREATE INDEX accounts_listing_idx
    ON accounts (created_at DESC, user_id DESC);

-- Reverse-lookup index for the optional declared-country filter; the partial
-- predicate keeps the index small while declared_country is mostly NULL.
CREATE INDEX accounts_declared_country_idx
    ON accounts (declared_country)
    WHERE declared_country IS NOT NULL;

-- blocked_emails persists pre-user blocked-email subjects that may exist
-- before any user account exists, plus the blocked subjects produced by
-- BlockByUserID/BlockByEmail. resolved_user_id is populated when the block
-- corresponds to an existing or formerly existing account.
CREATE TABLE blocked_emails (
    email             text PRIMARY KEY,
    reason_code       text NOT NULL,
    blocked_at        timestamptz NOT NULL,
    actor_type        text,
    actor_id          text,
    resolved_user_id  text
);

-- entitlement_records stores the immutable history of entitlement periods.
-- Each row represents one segment that was current at some point; closed
-- segments carry closed_* metadata.
CREATE TABLE entitlement_records (
    record_id           text PRIMARY KEY,
    user_id             text NOT NULL REFERENCES accounts(user_id),
    plan_code           text NOT NULL,
    source              text NOT NULL,
    actor_type          text NOT NULL,
    actor_id            text,
    reason_code         text NOT NULL,
    starts_at           timestamptz NOT NULL,
    ends_at             timestamptz,
    created_at          timestamptz NOT NULL,
    closed_at           timestamptz,
    closed_by_type      text,
    closed_by_id        text,
    closed_reason_code  text
);

CREATE INDEX entitlement_records_user_idx
    ON entitlement_records (user_id, created_at DESC);

-- entitlement_snapshots stores the read-optimized current entitlement state.
-- Exactly one row per user_id; updated atomically together with history rows
-- by EntitlementLifecycleStore operations.
CREATE TABLE entitlement_snapshots (
    user_id      text PRIMARY KEY REFERENCES accounts(user_id),
    plan_code    text NOT NULL,
    is_paid      boolean NOT NULL,
    starts_at    timestamptz NOT NULL,
    ends_at      timestamptz,
    source       text NOT NULL,
    actor_type   text NOT NULL,
    actor_id     text,
    reason_code  text NOT NULL,
    updated_at   timestamptz NOT NULL
);

-- Coarse free-versus-paid filter used by the admin listing surface.
CREATE INDEX entitlement_snapshots_paid_state_idx
    ON entitlement_snapshots (is_paid, plan_code);

-- Finite paid-expiry filter; partial predicate keeps the index limited to
-- finite paid plans (paid_monthly, paid_yearly).
CREATE INDEX entitlement_snapshots_paid_expiry_idx
    ON entitlement_snapshots (ends_at)
    WHERE is_paid AND ends_at IS NOT NULL;

-- sanction_records stores the immutable history of sanction mutations.
-- A row may carry removed_at + removed_* fields once the sanction is lifted.
CREATE TABLE sanction_records (
    record_id            text PRIMARY KEY,
    user_id              text NOT NULL REFERENCES accounts(user_id),
    sanction_code        text NOT NULL,
    scope                text NOT NULL,
    reason_code          text NOT NULL,
    actor_type           text NOT NULL,
    actor_id             text,
    applied_at           timestamptz NOT NULL,
    expires_at           timestamptz,
    removed_at           timestamptz,
    removed_by_type      text,
    removed_by_id        text,
    removed_reason_code  text
);

CREATE INDEX sanction_records_user_idx
    ON sanction_records (user_id, applied_at DESC);

-- sanction_active stores the at-most-one active record per (user_id,
-- sanction_code). It is maintained by PolicyLifecycleStore in the same
-- transaction as the corresponding sanction_records mutation.
CREATE TABLE sanction_active (
    user_id        text NOT NULL REFERENCES accounts(user_id),
    sanction_code  text NOT NULL,
    record_id      text NOT NULL REFERENCES sanction_records(record_id),
    PRIMARY KEY (user_id, sanction_code)
);

CREATE INDEX sanction_active_code_idx
    ON sanction_active (sanction_code);

-- limit_records mirrors sanction_records for user-specific limit overrides.
CREATE TABLE limit_records (
    record_id            text PRIMARY KEY,
    user_id              text NOT NULL REFERENCES accounts(user_id),
    limit_code           text NOT NULL,
    value                integer NOT NULL,
    reason_code          text NOT NULL,
    actor_type           text NOT NULL,
    actor_id             text,
    applied_at           timestamptz NOT NULL,
    expires_at           timestamptz,
    removed_at           timestamptz,
    removed_by_type      text,
    removed_by_id        text,
    removed_reason_code  text
);

CREATE INDEX limit_records_user_idx
    ON limit_records (user_id, applied_at DESC);

-- limit_active mirrors sanction_active for user-specific limits. value is
-- denormalised so the admin listing predicate can read it without joining
-- the full history.
CREATE TABLE limit_active (
    user_id     text NOT NULL REFERENCES accounts(user_id),
    limit_code  text NOT NULL,
    record_id   text NOT NULL REFERENCES limit_records(record_id),
    value       integer NOT NULL,
    PRIMARY KEY (user_id, limit_code)
);

CREATE INDEX limit_active_code_idx
    ON limit_active (limit_code);

-- +goose Down
DROP TABLE IF EXISTS limit_active;
DROP TABLE IF EXISTS limit_records;
DROP TABLE IF EXISTS sanction_active;
DROP TABLE IF EXISTS sanction_records;
DROP TABLE IF EXISTS entitlement_snapshots;
DROP TABLE IF EXISTS entitlement_records;
DROP TABLE IF EXISTS blocked_emails;
DROP TABLE IF EXISTS accounts;
