-- migrate:up

-- Enums
CREATE TYPE org_role AS ENUM ('owner', 'admin', 'member');
CREATE TYPE passkey_device_type AS ENUM ('singleDevice', 'multiDevice');

-- Users (authentication)
CREATE TABLE users (
    id              SERIAL PRIMARY KEY,
    email           TEXT UNIQUE NOT NULL,
    email_verified_at TIMESTAMPTZ,       -- NULL until verified
    full_name       TEXT,                -- Legal/full name (set in profile setup)
    display_name    TEXT,                -- Preferred display name (set in profile setup)
    phone_number    TEXT,                -- E.164 format, validated with libphonenumber-js
    avatar_url      TEXT,
    password_hash   TEXT,                -- NULL if using passkey only
    require_passkey BOOLEAN NOT NULL DEFAULT false,  -- If true, must use passkey to login
    is_superuser   BOOLEAN NOT NULL DEFAULT false,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Email verification tokens
CREATE TABLE email_verifications (
    id              BIGSERIAL PRIMARY KEY,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    token           TEXT UNIQUE NOT NULL,
    expires_at      TIMESTAMPTZ NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Password reset tokens
CREATE TABLE password_resets (
    id              BIGSERIAL PRIMARY KEY,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    token           TEXT UNIQUE NOT NULL,
    expires_at      TIMESTAMPTZ NOT NULL,
    used_at         TIMESTAMPTZ,             -- Set when token is used
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- WebAuthn challenges (short-lived, for passkey registration/authentication)
CREATE TABLE webauthn_challenges (
    id              BIGSERIAL PRIMARY KEY,
    options         JSONB NOT NULL,      -- Full options object from simplewebauthn
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Passkeys (WebAuthn credentials)
CREATE TABLE passkeys (
    id              BIGSERIAL PRIMARY KEY,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    credential_id   BYTEA UNIQUE NOT NULL,
    public_key      BYTEA NOT NULL,
    webauthn_user_id TEXT UNIQUE NOT NULL,
    counter         BIGINT NOT NULL DEFAULT 0,
    device_type     passkey_device_type NOT NULL,
    backup_eligible BOOLEAN NOT NULL,
    backup_status   BOOLEAN NOT NULL,
    transports      JSONB,               -- ['usb', 'ble', 'nfc', 'internal']
    rpid            TEXT NOT NULL,
    name            TEXT NOT NULL,       -- e.g. "MacBook Touch ID"
    last_used_at    TIMESTAMPTZ,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- User devices (tracks all devices a user has logged in from)
-- Note: device_fingerprint is NOT unique - multiple users can share the same
-- browser/device (e.g., shared family computer, work terminal)
CREATE TABLE user_devices (
    id              BIGSERIAL PRIMARY KEY,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    device_fingerprint TEXT NOT NULL,        -- Random ID stored in cookie
    name            TEXT,                    -- e.g. "Chrome on MacOS"
    is_trusted      BOOLEAN NOT NULL DEFAULT false,
    user_agent      TEXT NOT NULL,
    ip_address      TEXT,
    city            TEXT,
    region          TEXT,
    country         TEXT,
    last_used_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE(user_id, device_fingerprint)      -- One device record per user+fingerprint
);

-- Sessions (token_hash stores SHA-256 hash of token, not raw token)
-- Sessions are kept for audit history, not deleted by cron
CREATE TABLE sessions (
    id              BIGSERIAL PRIMARY KEY,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    device_id       BIGINT REFERENCES user_devices(id) ON DELETE SET NULL,
    token_hash      TEXT UNIQUE NOT NULL,    -- SHA-256 hash of session token
    trusted_mode    BOOLEAN NOT NULL,        -- True if passkey or email-confirmed login
    ip_address      TEXT,
    city            TEXT,
    region          TEXT,
    country         TEXT,
    user_agent      TEXT,
    expires_at      TIMESTAMPTZ NOT NULL,
    revoked_at      TIMESTAMPTZ,             -- NULL if active, set when logged out
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- API tokens (for CLI and programmatic access)
CREATE TABLE api_tokens (
    id              BIGSERIAL PRIMARY KEY,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    name            TEXT NOT NULL,           -- e.g. "CLI token", "CI/CD"
    token_hash      TEXT UNIQUE NOT NULL,    -- SHA-256 hash of token
    last_used_at    TIMESTAMPTZ,
    expires_at      TIMESTAMPTZ NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Login requests (tracks login attempts for polling and email confirmation)
-- Deleted after successful login via loginIfRequestIsCompleted()
-- Note: Only created for existing users. Non-existent users get a fake token (anti-enumeration).
CREATE TABLE login_requests (
    id              BIGSERIAL PRIMARY KEY,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    email           TEXT NOT NULL,           -- Email entered by user (normalized lowercase)
    token           TEXT UNIQUE,             -- Token sent in email link (NULL until email sent)
    device_fingerprint TEXT,                 -- From cookie at login request time
    ip_address      TEXT,
    city            TEXT,
    region          TEXT,
    country         TEXT,
    user_agent      TEXT,
    completed_at    TIMESTAMPTZ,             -- NULL until login completed
    expires_at      TIMESTAMPTZ NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Orgs (companies/accounts)
CREATE TABLE orgs (
    id              SERIAL PRIMARY KEY,
    slug            TEXT UNIQUE NOT NULL,
    display_name    TEXT NOT NULL,
    logo_url        TEXT,                -- Cloudflare R2 URL
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Org members (users belonging to orgs)
CREATE TABLE org_members (
    id              SERIAL PRIMARY KEY,
    org_id          INTEGER NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    role            org_role NOT NULL DEFAULT 'member',
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE(org_id, user_id)
);

-- Org sites (maps orgs to domains for report access)
CREATE TABLE org_sites (
    id              SERIAL PRIMARY KEY,
    org_id          INTEGER NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
    domain          TEXT NOT NULL,       -- key used to filter reports
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE(domain)
);

-- Org invites (pending invitations)
CREATE TABLE org_invites (
    id              SERIAL PRIMARY KEY,
    org_id          INTEGER NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
    email           TEXT NOT NULL,
    role            org_role NOT NULL DEFAULT 'member',
    invited_by      INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    token           TEXT UNIQUE NOT NULL,
    expires_at      TIMESTAMPTZ NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE(org_id, email)
);

-- Indexes (UNIQUE constraints already create indexes, so we skip those)
-- Note: orgs.slug has an index via UNIQUE constraint (used for all org API lookups)
-- Lookup indexes
CREATE INDEX idx_passkeys_user ON passkeys(user_id);
CREATE INDEX idx_user_devices_user ON user_devices(user_id);
CREATE INDEX idx_user_devices_fingerprint ON user_devices(device_fingerprint);
CREATE INDEX idx_sessions_user ON sessions(user_id);
CREATE INDEX idx_sessions_device ON sessions(device_id);
CREATE INDEX idx_api_tokens_user ON api_tokens(user_id);
CREATE INDEX idx_login_requests_user ON login_requests(user_id);
CREATE INDEX idx_password_resets_user ON password_resets(user_id);
CREATE INDEX idx_org_members_user ON org_members(user_id);
CREATE INDEX idx_org_members_org ON org_members(org_id);
CREATE INDEX idx_org_sites_org ON org_sites(org_id);
CREATE INDEX idx_org_invites_email ON org_invites(email);

-- Active session lookup (excludes revoked sessions)
CREATE INDEX idx_sessions_active ON sessions(token_hash) WHERE revoked_at IS NULL;

-- Cleanup job indexes (for deleting expired rows)
-- Note: sessions are NOT deleted, kept for audit history (use revoked_at instead)
CREATE INDEX idx_api_tokens_expires ON api_tokens(expires_at);
CREATE INDEX idx_email_verifications_expires ON email_verifications(expires_at);
CREATE INDEX idx_password_resets_expires ON password_resets(expires_at);
CREATE INDEX idx_webauthn_challenges_created ON webauthn_challenges(created_at);
CREATE INDEX idx_org_invites_expires ON org_invites(expires_at);
CREATE INDEX idx_login_requests_expires ON login_requests(expires_at);

-- migrate:down

DROP TABLE IF EXISTS org_invites;
DROP TABLE IF EXISTS org_sites;
DROP TABLE IF EXISTS org_members;
DROP TABLE IF EXISTS orgs;
DROP TABLE IF EXISTS login_requests;
DROP TABLE IF EXISTS api_tokens;
DROP TABLE IF EXISTS sessions;
DROP TABLE IF EXISTS user_devices;
DROP TABLE IF EXISTS passkeys;
DROP TABLE IF EXISTS webauthn_challenges;
DROP TABLE IF EXISTS password_resets;
DROP TABLE IF EXISTS email_verifications;
DROP TABLE IF EXISTS users;
DROP TYPE IF EXISTS passkey_device_type;
DROP TYPE IF EXISTS org_role;
