-- PaperPhone IM — Database Schema
-- MySQL 8.0+  |  utf8mb4  |  InnoDB
--
-- Usage:
--   Manual    : mysql -u root -p paperphone < schema.sql
--   Docker    : mounted into /docker-entrypoint-initdb.d/
--   Server    : auto-executed on startup via index.js / initMomentsTables()
--
-- Note: CREATE DATABASE / USE are intentionally omitted.
-- The target database must already be selected before importing.

-- ── Users ─────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS users (
  id          VARCHAR(36)   PRIMARY KEY,
  username    VARCHAR(64)   NOT NULL UNIQUE,
  nickname    VARCHAR(128)  NOT NULL,
  avatar      VARCHAR(512)  DEFAULT NULL,
  password    VARCHAR(255)  NOT NULL,
  -- ECDH Identity Key (Curve25519 public key, base64)
  ik_pub      TEXT          NOT NULL,
  -- Signed PreKey (Curve25519 public, base64)
  spk_pub     TEXT          NOT NULL,
  spk_sig     TEXT          NOT NULL,
  -- ML-KEM-768 long-term public key (base64)
  kem_pub     TEXT          NOT NULL,
  created_at  DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
  last_seen   DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
  is_online   TINYINT(1)    NOT NULL DEFAULT 0,
  INDEX idx_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── One-Time PreKeys (X3DH OPKs) ─────────────────────────────────────────
CREATE TABLE IF NOT EXISTS prekeys (
  id          INT UNSIGNED    AUTO_INCREMENT PRIMARY KEY,
  user_id     VARCHAR(36)     NOT NULL,
  key_id      INT             NOT NULL,
  opk_pub     TEXT            NOT NULL,
  used        TINYINT(1)      NOT NULL DEFAULT 0,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
  INDEX idx_user_unused (user_id, used)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── Friendships ───────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS friends (
  id          INT UNSIGNED    AUTO_INCREMENT PRIMARY KEY,
  user_id     VARCHAR(36)     NOT NULL,
  friend_id   VARCHAR(36)     NOT NULL,
  status      ENUM('pending','accepted','blocked') NOT NULL DEFAULT 'pending',
  auto_delete INT             NOT NULL DEFAULT 604800,
  created_at  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uk_pair (user_id, friend_id),
  FOREIGN KEY (user_id)   REFERENCES users(id) ON DELETE CASCADE,
  FOREIGN KEY (friend_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Migration: add auto_delete to friends (idempotent)
SET @f_ad = (SELECT COUNT(*) FROM information_schema.COLUMNS
  WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'friends' AND COLUMN_NAME = 'auto_delete');
SET @f_sql = IF(@f_ad = 0,
  'ALTER TABLE friends ADD COLUMN auto_delete INT NOT NULL DEFAULT 604800 AFTER status',
  'SELECT 1');
PREPARE f_stmt FROM @f_sql;
EXECUTE f_stmt;
DEALLOCATE PREPARE f_stmt;

-- Migration: add message to friends (idempotent) — friend request message (≤512 chars)
SET @f_msg = (SELECT COUNT(*) FROM information_schema.COLUMNS
  WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'friends' AND COLUMN_NAME = 'message');
SET @f_msg_sql = IF(@f_msg = 0,
  'ALTER TABLE friends ADD COLUMN message VARCHAR(512) DEFAULT NULL AFTER auto_delete',
  'SELECT 1');
PREPARE f_msg_stmt FROM @f_msg_sql;
EXECUTE f_msg_stmt;
DEALLOCATE PREPARE f_msg_stmt;

-- ── Groups ────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS `groups` (
  id          VARCHAR(36)   PRIMARY KEY,
  name        VARCHAR(128)  NOT NULL,
  avatar      VARCHAR(512)  DEFAULT NULL,
  owner_id    VARCHAR(36)   NOT NULL,
  notice      TEXT          DEFAULT NULL,
  auto_delete INT           NOT NULL DEFAULT 604800,
  created_at  DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (owner_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Migration: add auto_delete to groups (idempotent)
SET @g_ad = (SELECT COUNT(*) FROM information_schema.COLUMNS
  WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'groups' AND COLUMN_NAME = 'auto_delete');
SET @g_sql = IF(@g_ad = 0,
  'ALTER TABLE `groups` ADD COLUMN auto_delete INT NOT NULL DEFAULT 604800 AFTER notice',
  'SELECT 1');
PREPARE g_stmt FROM @g_sql;
EXECUTE g_stmt;
DEALLOCATE PREPARE g_stmt;

-- ── Group Members ─────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS group_members (
  group_id    VARCHAR(36)   NOT NULL,
  user_id     VARCHAR(36)   NOT NULL,
  role        ENUM('owner','admin','member') NOT NULL DEFAULT 'member',
  muted       TINYINT(1)    NOT NULL DEFAULT 0,
  joined_at   DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (group_id, user_id),
  FOREIGN KEY (group_id) REFERENCES `groups`(id) ON DELETE CASCADE,
  FOREIGN KEY (user_id)  REFERENCES users(id)    ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Migration: add muted column to group_members (idempotent)
SET @gm_muted = (SELECT COUNT(*) FROM information_schema.COLUMNS
  WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'group_members' AND COLUMN_NAME = 'muted');
SET @gm_sql = IF(@gm_muted = 0,
  'ALTER TABLE group_members ADD COLUMN muted TINYINT(1) NOT NULL DEFAULT 0 AFTER role',
  'SELECT 1');
PREPARE gm_stmt FROM @gm_sql;
EXECUTE gm_stmt;
DEALLOCATE PREPARE gm_stmt;

-- ── Messages ──────────────────────────────────────────────────────────────
-- Server stores encrypted payloads for offline delivery only.
-- Once delivered via WebSocket, messages may be pruned.
CREATE TABLE IF NOT EXISTS messages (
  id          VARCHAR(36)   PRIMARY KEY,
  type        ENUM('private','group') NOT NULL,
  from_id     VARCHAR(36)   NOT NULL,
  to_id       VARCHAR(36)   NOT NULL,   -- user_id or group_id
  ciphertext  LONGTEXT      NOT NULL,   -- base64 encrypted payload (for recipient)
  header      TEXT          DEFAULT NULL, -- ephemeral public key (for E2EE, recipient)
  self_ciphertext LONGTEXT  DEFAULT NULL, -- base64 encrypted payload (for sender)
  self_header TEXT          DEFAULT NULL, -- ephemeral public key (for E2EE, sender)
  msg_type    ENUM('text','image','file','voice','video_call','system','sticker') NOT NULL DEFAULT 'text',
  created_at  DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
  delivered   TINYINT(1)    NOT NULL DEFAULT 0,
  read_at     DATETIME      DEFAULT NULL,
  INDEX idx_to_undelivered (to_id, delivered, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Migration: add self_ciphertext / self_header for dual encryption (idempotent)
SET @db_name = DATABASE();
SET @tbl = 'messages';

SET @col_check = (SELECT COUNT(*) FROM information_schema.COLUMNS
  WHERE TABLE_SCHEMA = @db_name AND TABLE_NAME = @tbl AND COLUMN_NAME = 'self_ciphertext');
SET @sql = IF(@col_check = 0,
  'ALTER TABLE messages ADD COLUMN self_ciphertext LONGTEXT DEFAULT NULL AFTER header, ADD COLUMN self_header TEXT DEFAULT NULL AFTER self_ciphertext',
  'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

-- Migration: add 'sticker' to msg_type ENUM (idempotent)
SET @mt_check = (SELECT COLUMN_TYPE FROM information_schema.COLUMNS
  WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'messages' AND COLUMN_NAME = 'msg_type');
SET @mt_sql = IF(@mt_check NOT LIKE '%sticker%',
  "ALTER TABLE messages MODIFY COLUMN msg_type ENUM('text','image','file','voice','video_call','system','sticker') NOT NULL DEFAULT 'text'",
  'SELECT 1');
PREPARE mt_stmt FROM @mt_sql;
EXECUTE mt_stmt;
DEALLOCATE PREPARE mt_stmt;

-- Migration: add 'video' to msg_type ENUM (idempotent)
SET @mt_vid = (SELECT COLUMN_TYPE FROM information_schema.COLUMNS
  WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'messages' AND COLUMN_NAME = 'msg_type');
SET @mt_vid_sql = IF(@mt_vid NOT LIKE '%video\'%',
  "ALTER TABLE messages MODIFY COLUMN msg_type ENUM('text','image','file','voice','video_call','system','sticker','video') NOT NULL DEFAULT 'text'",
  'SELECT 1');
PREPARE mt_vid_stmt FROM @mt_vid_sql;
EXECUTE mt_vid_stmt;
DEALLOCATE PREPARE mt_vid_stmt;

-- ── Moments (朋友圈) ──────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS moments (
  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  user_id     VARCHAR(36)   NOT NULL,
  text_content VARCHAR(1024) NOT NULL DEFAULT '',
  created_at  DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_user_id (user_id),
  INDEX idx_created_at (created_at),
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── Moment Images ─────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS moment_images (
  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  moment_id   BIGINT UNSIGNED NOT NULL,
  url         TEXT            NOT NULL,
  sort_order  TINYINT UNSIGNED NOT NULL DEFAULT 0,
  INDEX idx_moment_id (moment_id),
  FOREIGN KEY (moment_id) REFERENCES moments(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── Moment Videos ─────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS moment_videos (
  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  moment_id   BIGINT UNSIGNED NOT NULL,
  url         TEXT            NOT NULL,
  thumbnail   TEXT            DEFAULT NULL,
  duration    SMALLINT UNSIGNED NOT NULL DEFAULT 0,
  INDEX idx_moment_id (moment_id),
  FOREIGN KEY (moment_id) REFERENCES moments(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── Moment Likes ──────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS moment_likes (
  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  moment_id   BIGINT UNSIGNED NOT NULL,
  user_id     VARCHAR(36)     NOT NULL,
  created_at  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_like (moment_id, user_id),
  FOREIGN KEY (moment_id) REFERENCES moments(id)  ON DELETE CASCADE,
  FOREIGN KEY (user_id)   REFERENCES users(id)    ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── Moment Comments ───────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS moment_comments (
  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  moment_id   BIGINT UNSIGNED NOT NULL,
  user_id     VARCHAR(36)     NOT NULL,
  text_content VARCHAR(512)   NOT NULL,
  created_at  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_moment_id (moment_id),
  FOREIGN KEY (moment_id) REFERENCES moments(id) ON DELETE CASCADE,
  FOREIGN KEY (user_id)   REFERENCES users(id)   ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── Push Subscriptions (Web Push / VAPID) ─────────────────────────────────
CREATE TABLE IF NOT EXISTS push_subscriptions (
  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  user_id     VARCHAR(36)     NOT NULL,
  endpoint    TEXT            NOT NULL,
  p256dh      TEXT            NOT NULL,
  auth        TEXT            NOT NULL,
  user_agent  VARCHAR(255)    DEFAULT NULL,
  created_at  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uk_user_endpoint (user_id, endpoint(512)),
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
  INDEX idx_push_user (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── OneSignal Players (Median.co native push) ────────────────────────────
CREATE TABLE IF NOT EXISTS onesignal_players (
  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  user_id     VARCHAR(36)     NOT NULL,
  player_id   VARCHAR(64)     NOT NULL,
  platform    VARCHAR(16)     DEFAULT NULL,
  created_at  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uk_player (player_id),
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
  INDEX idx_os_user (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── Sessions (Login Device Tracking) ─────────────────────────────────────
CREATE TABLE IF NOT EXISTS sessions (
  id           VARCHAR(36)   PRIMARY KEY,
  user_id      VARCHAR(36)   NOT NULL,
  device_name  VARCHAR(128)  DEFAULT NULL,
  device_type  VARCHAR(16)   DEFAULT NULL,
  os           VARCHAR(64)   DEFAULT NULL,
  browser      VARCHAR(64)   DEFAULT NULL,
  ip_address   VARCHAR(45)   DEFAULT NULL,
  last_active  DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_at   DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
  revoked      TINYINT(1)    NOT NULL DEFAULT 0,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
  INDEX idx_sess_user (user_id, revoked)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── Friend Tags ──────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS friend_tags (
  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  user_id     VARCHAR(36)   NOT NULL,
  name        VARCHAR(32)   NOT NULL,
  color       VARCHAR(7)    DEFAULT '#2196F3',
  created_at  DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uk_user_tag (user_id, name),
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
  INDEX idx_ft_user (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── Friend Tag Assignments (many-to-many) ────────────────────────────────
CREATE TABLE IF NOT EXISTS friend_tag_assignments (
  tag_id      BIGINT UNSIGNED NOT NULL,
  friend_id   VARCHAR(36)     NOT NULL,
  PRIMARY KEY (tag_id, friend_id),
  FOREIGN KEY (tag_id)    REFERENCES friend_tags(id) ON DELETE CASCADE,
  FOREIGN KEY (friend_id) REFERENCES users(id)       ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── Moment Visibility Rules ──────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS moment_visibility (
  moment_id   BIGINT UNSIGNED NOT NULL,
  type        ENUM('whitelist','blacklist') NOT NULL,
  target_type ENUM('tag','user') NOT NULL,
  target_id   VARCHAR(36) NOT NULL,
  PRIMARY KEY (moment_id, target_type, target_id),
  FOREIGN KEY (moment_id) REFERENCES moments(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Migration: add visibility column to moments (idempotent)
SET @m_vis = (SELECT COUNT(*) FROM information_schema.COLUMNS
  WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'moments' AND COLUMN_NAME = 'visibility');
SET @m_sql = IF(@m_vis = 0,
  "ALTER TABLE moments ADD COLUMN visibility ENUM('public','whitelist','blacklist') NOT NULL DEFAULT 'public' AFTER text_content",
  'SELECT 1');
PREPARE m_stmt FROM @m_sql;
EXECUTE m_stmt;
DEALLOCATE PREPARE m_stmt;

-- ── TOTP Two-Factor Authentication ───────────────────────────────────────
CREATE TABLE IF NOT EXISTS user_totp (
  user_id         VARCHAR(36)   PRIMARY KEY,
  totp_secret     VARCHAR(64)   NOT NULL,
  recovery_codes  TEXT          DEFAULT NULL,
  enabled         TINYINT(1)    NOT NULL DEFAULT 0,
  created_at      DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── Moment Privacy (user-level moments visibility) ──────────────────────
CREATE TABLE IF NOT EXISTS moment_privacy (
  user_id     VARCHAR(36)   NOT NULL,
  target_id   VARCHAR(36)   NOT NULL,
  hide_their  TINYINT(1)    NOT NULL DEFAULT 0,
  hide_mine   TINYINT(1)    NOT NULL DEFAULT 0,
  PRIMARY KEY (user_id, target_id),
  FOREIGN KEY (user_id)   REFERENCES users(id) ON DELETE CASCADE,
  FOREIGN KEY (target_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── Timeline (public posts, Xiaohongshu-style) ──────────────────────────
CREATE TABLE IF NOT EXISTS timeline_posts (
  id            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  user_id       VARCHAR(36)     NOT NULL,
  text_content  VARCHAR(2000)   NOT NULL DEFAULT '',
  is_anonymous  TINYINT(1)      NOT NULL DEFAULT 0,
  created_at    DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_tl_created (created_at),
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS timeline_media (
  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  post_id     BIGINT UNSIGNED NOT NULL,
  url         TEXT            NOT NULL,
  media_type  ENUM('image','video') NOT NULL DEFAULT 'image',
  thumbnail   TEXT            DEFAULT NULL,
  duration    SMALLINT UNSIGNED NOT NULL DEFAULT 0,
  sort_order  TINYINT UNSIGNED NOT NULL DEFAULT 0,
  INDEX idx_tl_media_post (post_id),
  FOREIGN KEY (post_id) REFERENCES timeline_posts(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS timeline_likes (
  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  post_id     BIGINT UNSIGNED NOT NULL,
  user_id     VARCHAR(36)     NOT NULL,
  created_at  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_tl_like (post_id, user_id),
  FOREIGN KEY (post_id) REFERENCES timeline_posts(id) ON DELETE CASCADE,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS timeline_comments (
  id            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  post_id       BIGINT UNSIGNED NOT NULL,
  user_id       VARCHAR(36)     NOT NULL,
  is_anonymous  TINYINT(1)      NOT NULL DEFAULT 0,
  text_content  VARCHAR(512)    NOT NULL,
  created_at    DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_tl_comment_post (post_id),
  FOREIGN KEY (post_id) REFERENCES timeline_posts(id) ON DELETE CASCADE,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── Group Invites (QR code invite links with expiration) ─────────────────
CREATE TABLE IF NOT EXISTS group_invites (
  id          VARCHAR(36)   PRIMARY KEY,
  group_id    VARCHAR(36)   NOT NULL,
  created_by  VARCHAR(36)   NOT NULL,
  expires_at  DATETIME      NOT NULL,
  created_at  DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (group_id)   REFERENCES `groups`(id) ON DELETE CASCADE,
  FOREIGN KEY (created_by) REFERENCES users(id)    ON DELETE CASCADE,
  INDEX idx_gi_group (group_id),
  INDEX idx_gi_expires (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
