import Database from 'better-sqlite3';
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// ANSI color codes for terminal output
const colors = {
    reset: '\x1b[0m',
    bright: '\x1b[1m',
    cyan: '\x1b[36m',
    dim: '\x1b[2m',
};

const c = {
    info: (text) => `${colors.cyan}${text}${colors.reset}`,
    bright: (text) => `${colors.bright}${text}${colors.reset}`,
    dim: (text) => `${colors.dim}${text}${colors.reset}`,
};

// Use DATABASE_PATH environment variable if set, otherwise use DATA_DIR/auth.db
// DATA_DIR defaults to ./data relative to project root
const PROJECT_ROOT = path.join(__dirname, '../..');
const DATA_DIR = process.env.DATA_DIR || path.join(PROJECT_ROOT, 'data');
const DB_PATH = process.env.DATABASE_PATH
  ? path.resolve(PROJECT_ROOT, process.env.DATABASE_PATH)
  : path.join(DATA_DIR, 'auth.db');
const INIT_SQL_PATH = path.join(__dirname, 'init.sql');

// Ensure database directory exists
const dbDir = path.dirname(DB_PATH);
try {
  if (!fs.existsSync(dbDir)) {
    fs.mkdirSync(dbDir, { recursive: true });
    console.log(`Created database directory: ${dbDir}`);
  }
} catch (error) {
  console.error(`Failed to create database directory ${dbDir}:`, error.message);
  throw error;
}

// Create database connection
const db = new Database(DB_PATH);

// Optimize SQLite for concurrent access
db.pragma('journal_mode = WAL');       // Write-Ahead Logging for concurrent reads during writes
db.pragma('busy_timeout = 5000');      // Wait up to 5s when database is locked instead of failing immediately
db.pragma('synchronous = NORMAL');     // Balance between safety and performance with WAL

// Show app installation path prominently
const appInstallPath = path.join(__dirname, '../..');
console.log('');
console.log(c.dim('═'.repeat(60)));
console.log(`${c.info('[INFO]')} App Installation: ${c.bright(appInstallPath)}`);
console.log(`${c.info('[INFO]')} Data Directory: ${c.dim(path.relative(appInstallPath, DATA_DIR))}`);
console.log(`${c.info('[INFO]')} Database: ${c.dim(path.relative(appInstallPath, DB_PATH))}`);
if (process.env.DATABASE_PATH) {
  console.log(`       ${c.dim('(Using custom DATABASE_PATH from environment)')}`);
}
console.log(c.dim('═'.repeat(60)));
console.log('');

const runMigrations = () => {
  try {
    const tableInfo = db.prepare("PRAGMA table_info(users)").all();
    const columnNames = tableInfo.map(col => col.name);

    // Create verification_codes table for email login
    db.exec(`
      CREATE TABLE IF NOT EXISTS verification_codes (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        email TEXT NOT NULL,
        code TEXT NOT NULL,
        type TEXT DEFAULT 'login' CHECK(type IN ('login', 'register')),
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        expires_at DATETIME NOT NULL,
        attempts INTEGER DEFAULT 0,
        used BOOLEAN DEFAULT 0,
        ip_address TEXT
      )
    `);
    db.exec('CREATE INDEX IF NOT EXISTS idx_verification_codes_email ON verification_codes(email)');
    db.exec('CREATE INDEX IF NOT EXISTS idx_verification_codes_expires ON verification_codes(expires_at)');

    // Create usage_records table for tracking token usage
    db.exec(`
      CREATE TABLE IF NOT EXISTS usage_records (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_uuid TEXT NOT NULL,
        session_id TEXT,
        model TEXT NOT NULL,
        input_tokens INTEGER DEFAULT 0,
        output_tokens INTEGER DEFAULT 0,
        cache_read_tokens INTEGER DEFAULT 0,
        cache_creation_tokens INTEGER DEFAULT 0,
        cost_usd REAL DEFAULT 0,
        source TEXT DEFAULT 'sdk',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
      )
    `);
    db.exec('CREATE INDEX IF NOT EXISTS idx_usage_records_user_uuid ON usage_records(user_uuid)');
    db.exec('CREATE INDEX IF NOT EXISTS idx_usage_records_created_at ON usage_records(created_at)');

    // Create usage_daily_summary table for aggregated stats
    db.exec(`
      CREATE TABLE IF NOT EXISTS usage_daily_summary (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_uuid TEXT NOT NULL,
        date TEXT NOT NULL,
        model TEXT NOT NULL,
        total_input_tokens INTEGER DEFAULT 0,
        total_output_tokens INTEGER DEFAULT 0,
        total_cost_usd REAL DEFAULT 0,
        session_count INTEGER DEFAULT 0,
        request_count INTEGER DEFAULT 0,
        UNIQUE(user_uuid, date, model)
      )
    `);
    db.exec('CREATE INDEX IF NOT EXISTS idx_usage_daily_summary_user_date ON usage_daily_summary(user_uuid, date)');

    if (!columnNames.includes('git_name')) {
      console.log('Running migration: Adding git_name column');
      db.exec('ALTER TABLE users ADD COLUMN git_name TEXT');
    }

    if (!columnNames.includes('git_email')) {
      console.log('Running migration: Adding git_email column');
      db.exec('ALTER TABLE users ADD COLUMN git_email TEXT');
    }

    if (!columnNames.includes('has_completed_onboarding')) {
      console.log('Running migration: Adding has_completed_onboarding column');
      db.exec('ALTER TABLE users ADD COLUMN has_completed_onboarding BOOLEAN DEFAULT 0');
    }

    // Add uuid column if not exists (without UNIQUE constraint - use index instead)
    if (!columnNames.includes('uuid')) {
      console.log('Running migration: Adding uuid column');
      db.exec('ALTER TABLE users ADD COLUMN uuid TEXT');
    }
    // Create unique index for uuid (safe to run even if already exists)
    db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_users_uuid ON users(uuid)');

    // Add role column if not exists
    if (!columnNames.includes('role')) {
      console.log('Running migration: Adding role column');
      db.exec("ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'user'");
    }
    // Create index for role (safe to run even if already exists)
    db.exec('CREATE INDEX IF NOT EXISTS idx_users_role ON users(role)');

    // Add status column if not exists
    if (!columnNames.includes('status')) {
      console.log('Running migration: Adding status column');
      db.exec("ALTER TABLE users ADD COLUMN status TEXT DEFAULT 'active'");
    }
    // Create index for status (safe to run even if already exists)
    db.exec('CREATE INDEX IF NOT EXISTS idx_users_status ON users(status)');

    // Add email column if not exists (for email verification login)
    if (!columnNames.includes('email')) {
      console.log('Running migration: Adding email column');
      db.exec('ALTER TABLE users ADD COLUMN email TEXT');
    }
    db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email ON users(email)');

    // Create email domain whitelist table
    db.exec(`
      CREATE TABLE IF NOT EXISTS email_domain_whitelist (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        domain TEXT UNIQUE NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        created_by INTEGER,
        FOREIGN KEY (created_by) REFERENCES users(id)
      )
    `);
    db.exec('CREATE INDEX IF NOT EXISTS idx_email_domain_whitelist_domain ON email_domain_whitelist(domain)');

    // Add spending limit columns for users
    if (!columnNames.includes('total_limit_usd')) {
      console.log('Running migration: Adding total_limit_usd column');
      db.exec('ALTER TABLE users ADD COLUMN total_limit_usd REAL DEFAULT NULL');
    }

    if (!columnNames.includes('daily_limit_usd')) {
      console.log('Running migration: Adding daily_limit_usd column');
      db.exec('ALTER TABLE users ADD COLUMN daily_limit_usd REAL DEFAULT NULL');
    }

    // Add feature_permissions column for per-user feature access control
    if (!columnNames.includes('feature_permissions')) {
      console.log('Running migration: Adding feature_permissions column');
      db.exec('ALTER TABLE users ADD COLUMN feature_permissions TEXT DEFAULT NULL');
    }

    // Add visible_agents column for per-user agent visibility control
    if (!columnNames.includes('visible_agents')) {
      console.log('Running migration: Adding visible_agents column');
      db.exec('ALTER TABLE users ADD COLUMN visible_agents TEXT DEFAULT NULL');
    }

    // Add raw_model column to usage_records for tracking actual model IDs
    const usageTableInfo = db.prepare("PRAGMA table_info(usage_records)").all();
    const usageColumnNames = usageTableInfo.map(col => col.name);

    if (!usageColumnNames.includes('raw_model')) {
      console.log('Running migration: Adding raw_model column to usage_records');
      db.exec('ALTER TABLE usage_records ADD COLUMN raw_model TEXT DEFAULT NULL');
    }

    // Create system_settings table for global configuration
    db.exec(`
      CREATE TABLE IF NOT EXISTS system_settings (
        key TEXT PRIMARY KEY,
        value TEXT,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_by INTEGER,
        FOREIGN KEY (updated_by) REFERENCES users(id)
      )
    `);

    // Create Feishu integration tables
    db.exec(`
      CREATE TABLE IF NOT EXISTS feishu_bindings (
        id             INTEGER PRIMARY KEY AUTOINCREMENT,
        feishu_open_id TEXT NOT NULL UNIQUE,
        user_uuid      TEXT NOT NULL,
        created_at     DATETIME DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (user_uuid) REFERENCES users(uuid)
      )
    `);
    db.exec('CREATE INDEX IF NOT EXISTS idx_feishu_open_id ON feishu_bindings(feishu_open_id)');

    db.exec(`
      CREATE TABLE IF NOT EXISTS feishu_session_state (
        feishu_open_id   TEXT PRIMARY KEY,
        claude_session_id TEXT,
        cwd              TEXT NOT NULL DEFAULT '',
        permission_mode  TEXT NOT NULL DEFAULT 'default',
        updated_at       DATETIME DEFAULT CURRENT_TIMESTAMP
      )
    `);

    // 为已有 feishu_session_state 表补充 chat_id 列（存储 p2p 会话的 chat_id，供菜单事件主动发消息使用）
    try {
      db.exec('ALTER TABLE feishu_session_state ADD COLUMN chat_id TEXT');
    } catch (_) {
      // 列已存在，忽略
    }

    // 为已有 feishu_session_state 表补充 claude_model 列
    try {
      db.exec("ALTER TABLE feishu_session_state ADD COLUMN claude_model TEXT NOT NULL DEFAULT 'sonnet'");
    } catch (_) {
      // 列已存在，忽略
    }

    // 飞书已处理消息 ID 去重表（持久化，防止服务重启后重复处理）
    db.exec(`
      CREATE TABLE IF NOT EXISTS feishu_processed_messages (
        message_id  TEXT PRIMARY KEY,
        processed_at DATETIME DEFAULT CURRENT_TIMESTAMP
      )
    `);

    // Migration: Add super_admin role (recreate users table with updated CHECK constraint)
    const hasAnyUser = db.prepare('SELECT COUNT(*) as count FROM users').get().count > 0;
    const hasSuperAdmin = db.prepare("SELECT COUNT(*) as count FROM users WHERE role = 'super_admin'").get().count > 0;

    if (hasAnyUser && !hasSuperAdmin) {
      console.log('Running migration: Adding super_admin role and promoting first admin');
      db.exec('PRAGMA foreign_keys = OFF');
      db.transaction(() => {
        db.exec(`CREATE TABLE users_new (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          username TEXT UNIQUE,
          password_hash TEXT,
          email TEXT UNIQUE,
          uuid TEXT,
          role TEXT DEFAULT 'user' CHECK(role IN ('admin', 'user', 'super_admin')),
          status TEXT DEFAULT 'active' CHECK(status IN ('active', 'disabled')),
          created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
          last_login DATETIME,
          is_active BOOLEAN DEFAULT 1,
          git_name TEXT,
          git_email TEXT,
          has_completed_onboarding BOOLEAN DEFAULT 0,
          total_limit_usd REAL DEFAULT NULL,
          daily_limit_usd REAL DEFAULT NULL
        )`);
        db.exec('INSERT INTO users_new SELECT id,username,password_hash,email,uuid,role,status,created_at,last_login,is_active,git_name,git_email,has_completed_onboarding,total_limit_usd,daily_limit_usd FROM users');
        db.exec('DROP TABLE users');
        db.exec('ALTER TABLE users_new RENAME TO users');
        db.exec("UPDATE users SET role='super_admin' WHERE id=(SELECT MIN(id) FROM users WHERE role='admin')");
      })();
      db.exec('PRAGMA foreign_keys = ON');
      db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_users_uuid ON users(uuid)');
      db.exec('CREATE INDEX IF NOT EXISTS idx_users_role ON users(role)');
      db.exec('CREATE INDEX IF NOT EXISTS idx_users_status ON users(status)');
      db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email ON users(email)');
    }

    // Agent submissions table for review workflow
    db.exec(`
      CREATE TABLE IF NOT EXISTS agent_submissions (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER NOT NULL,
        agent_name TEXT NOT NULL,
        display_name TEXT NOT NULL,
        description TEXT,
        zip_path TEXT NOT NULL,
        status TEXT NOT NULL DEFAULT 'pending'
          CHECK(status IN ('pending', 'approved', 'rejected')),
        reject_reason TEXT,
        version TEXT,
        submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        reviewed_at DATETIME,
        reviewed_by INTEGER,
        FOREIGN KEY (user_id) REFERENCES users(id),
        FOREIGN KEY (reviewed_by) REFERENCES users(id)
      )
    `);
    db.exec('CREATE INDEX IF NOT EXISTS idx_agent_submissions_user ON agent_submissions(user_id)');
    db.exec('CREATE INDEX IF NOT EXISTS idx_agent_submissions_status ON agent_submissions(status)');

    // Migration: add update_notes column
    try {
      db.exec('ALTER TABLE agent_submissions ADD COLUMN update_notes TEXT');
    } catch (_) { /* 列已存在 */ }

    // 定时任务表（Cron 工具服务端接管）
    db.exec(`
      CREATE TABLE IF NOT EXISTS cron_tasks (
        id             TEXT PRIMARY KEY,
        cron_expr      TEXT NOT NULL,
        prompt         TEXT NOT NULL,
        recurring      INTEGER NOT NULL DEFAULT 1,
        user_uuid      TEXT NOT NULL,
        session_id     TEXT,
        project_path   TEXT,
        session_mode   TEXT NOT NULL DEFAULT 'new_session',
        channel        TEXT NOT NULL DEFAULT '',
        feishu_open_id TEXT,
        chat_id        TEXT,
        webhook_url    TEXT,
        created_at     INTEGER NOT NULL,
        last_fired_at  INTEGER,
        is_paused      INTEGER NOT NULL DEFAULT 0
      )
    `);
    db.exec('CREATE INDEX IF NOT EXISTS idx_cron_tasks_user ON cron_tasks(user_uuid)');

    // 会话分享表
    db.exec(`
      CREATE TABLE IF NOT EXISTS session_shares (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_uuid TEXT NOT NULL,
        session_id TEXT NOT NULL,
        project_name TEXT NOT NULL,
        share_mode TEXT NOT NULL DEFAULT 'realtime',
        status TEXT NOT NULL DEFAULT 'active',
        session_title TEXT,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        UNIQUE(user_uuid, session_id)
      )
    `);
    db.exec('CREATE INDEX IF NOT EXISTS idx_session_shares_session ON session_shares(session_id)');
    db.exec('CREATE INDEX IF NOT EXISTS idx_session_shares_user ON session_shares(user_uuid)');

    // 聊天图片持久化关联表
    db.exec(`
      CREATE TABLE IF NOT EXISTS message_images (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        upload_batch_id TEXT NOT NULL,
        user_uuid TEXT NOT NULL,
        session_id TEXT,
        message_content TEXT,
        original_name TEXT NOT NULL,
        file_hash TEXT NOT NULL,
        file_ext TEXT NOT NULL,
        file_size INTEGER NOT NULL,
        mime_type TEXT NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
      )
    `);
    db.exec('CREATE INDEX IF NOT EXISTS idx_msg_images_batch ON message_images(upload_batch_id)');
    db.exec('CREATE INDEX IF NOT EXISTS idx_msg_images_session ON message_images(session_id)');
    db.exec('CREATE INDEX IF NOT EXISTS idx_msg_images_created ON message_images(created_at)');

    console.log('Database migrations completed successfully');
  } catch (error) {
    console.error('Error running migrations:', error.message);
    throw error;
  }
};

// Initialize database with schema
const initializeDatabase = async () => {
  try {
    const initSQL = fs.readFileSync(INIT_SQL_PATH, 'utf8');
    db.exec(initSQL);
    console.log('Database initialized successfully');
    runMigrations();
  } catch (error) {
    console.error('Error initializing database:', error.message);
    throw error;
  }
};

// User database operations
const userDb = {
  // Check if any users exist
  hasUsers: () => {
    try {
      const row = db.prepare('SELECT COUNT(*) as count FROM users').get();
      return row.count > 0;
    } catch (err) {
      throw err;
    }
  },

  // Create a new user
  createUser: (username, passwordHash) => {
    try {
      const stmt = db.prepare('INSERT INTO users (username, password_hash) VALUES (?, ?)');
      const result = stmt.run(username, passwordHash);
      return { id: result.lastInsertRowid, username };
    } catch (err) {
      throw err;
    }
  },

  // Get user by username
  getUserByUsername: (username) => {
    try {
      const row = db.prepare('SELECT * FROM users WHERE username = ? AND is_active = 1').get(username);
      return row;
    } catch (err) {
      throw err;
    }
  },

  // Update last login time
  updateLastLogin: (userId) => {
    try {
      db.prepare('UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?').run(userId);
    } catch (err) {
      throw err;
    }
  },

  // Get user by ID
  getUserById: (userId) => {
    try {
      const row = db.prepare('SELECT id, username, uuid, role, status, created_at, last_login FROM users WHERE id = ? AND is_active = 1').get(userId);
      return row;
    } catch (err) {
      throw err;
    }
  },

  // Get user by ID with password hash (for password verification)
  getUserByIdWithPassword: (userId) => {
    try {
      const row = db.prepare('SELECT id, username, email, uuid, role, status, password_hash FROM users WHERE id = ? AND is_active = 1').get(userId);
      return row;
    } catch (err) {
      throw err;
    }
  },

  // Update user password
  updateUserPassword: (userId, passwordHash) => {
    try {
      const stmt = db.prepare('UPDATE users SET password_hash = ? WHERE id = ?');
      return stmt.run(passwordHash, userId);
    } catch (err) {
      throw err;
    }
  },

  getFirstUser: () => {
    try {
      const row = db.prepare('SELECT id, username, created_at, last_login FROM users WHERE is_active = 1 LIMIT 1').get();
      return row;
    } catch (err) {
      throw err;
    }
  },

  updateGitConfig: (userId, gitName, gitEmail) => {
    try {
      const stmt = db.prepare('UPDATE users SET git_name = ?, git_email = ? WHERE id = ?');
      stmt.run(gitName, gitEmail, userId);
    } catch (err) {
      throw err;
    }
  },

  getGitConfig: (userId) => {
    try {
      const row = db.prepare('SELECT git_name, git_email FROM users WHERE id = ?').get(userId);
      return row;
    } catch (err) {
      throw err;
    }
  },

  completeOnboarding: (userId) => {
    try {
      const stmt = db.prepare('UPDATE users SET has_completed_onboarding = 1 WHERE id = ?');
      stmt.run(userId);
    } catch (err) {
      throw err;
    }
  },

  hasCompletedOnboarding: (userId) => {
    try {
      const row = db.prepare('SELECT has_completed_onboarding FROM users WHERE id = ?').get(userId);
      return row?.has_completed_onboarding === 1;
    } catch (err) {
      throw err;
    }
  },

  // Get user count
  getUserCount: () => {
    try {
      const row = db.prepare('SELECT COUNT(*) as count FROM users').get();
      return row.count;
    } catch (err) {
      throw err;
    }
  },

  // Create user with full details
  createUserFull: (username, passwordHash, uuid, role) => {
    try {
      const stmt = db.prepare(
        'INSERT INTO users (username, password_hash, uuid, role) VALUES (?, ?, ?, ?)'
      );
      const result = stmt.run(username, passwordHash, uuid, role);
      return { id: result.lastInsertRowid, username, uuid, role };
    } catch (err) {
      throw err;
    }
  },

  // Get all users (for admin)
  getAllUsers: () => {
    try {
      return db.prepare(
        'SELECT id, username, email, uuid, role, status, created_at, last_login, total_limit_usd, daily_limit_usd, feature_permissions, visible_agents FROM users ORDER BY created_at DESC'
      ).all();
    } catch (err) {
      throw err;
    }
  },

  // Update user role
  updateUserRole: (userId, role) => {
    try {
      db.prepare('UPDATE users SET role = ? WHERE id = ?').run(role, userId);
    } catch (err) {
      throw err;
    }
  },

  // Update user status
  updateUserStatus: (userId, status) => {
    try {
      db.prepare('UPDATE users SET status = ? WHERE id = ?').run(status, userId);
    } catch (err) {
      throw err;
    }
  },

  // Delete user by ID
  deleteUserById: (userId) => {
    try {
      db.prepare('DELETE FROM users WHERE id = ?').run(userId);
    } catch (err) {
      throw err;
    }
  },

  // Get user by UUID
  getUserByUuid: (uuid) => {
    try {
      return db.prepare('SELECT * FROM users WHERE uuid = ?').get(uuid);
    } catch (err) {
      throw err;
    }
  },

  // Get user by email
  getUserByEmail: (email) => {
    try {
      return db.prepare('SELECT * FROM users WHERE email = ? AND is_active = 1').get(email);
    } catch (err) {
      throw err;
    }
  },

  // Create user with email (for email verification login)
  createUserWithEmail: (email, uuid, role) => {
    try {
      const stmt = db.prepare(
        'INSERT INTO users (email, uuid, role) VALUES (?, ?, ?)'
      );
      const result = stmt.run(email, uuid, role);
      return { id: result.lastInsertRowid, email, uuid, role };
    } catch (err) {
      throw err;
    }
  },

  // Check if email exists
  emailExists: (email) => {
    try {
      const row = db.prepare('SELECT COUNT(*) as count FROM users WHERE email = ?').get(email);
      return row.count > 0;
    } catch (err) {
      throw err;
    }
  },

  // Update user spending limits
  updateUserLimits: (userId, totalLimit, dailyLimit) => {
    try {
      db.prepare('UPDATE users SET total_limit_usd = ?, daily_limit_usd = ? WHERE id = ?')
        .run(totalLimit, dailyLimit, userId);
    } catch (err) {
      throw err;
    }
  },

  // Get user spending limits
  getUserLimits: (userId) => {
    try {
      return db.prepare('SELECT total_limit_usd, daily_limit_usd FROM users WHERE id = ?').get(userId);
    } catch (err) {
      throw err;
    }
  },

  // Get user limits by UUID
  getUserLimitsByUuid: (uuid) => {
    try {
      return db.prepare('SELECT total_limit_usd, daily_limit_usd FROM users WHERE uuid = ?').get(uuid);
    } catch (err) {
      throw err;
    }
  },

  // Get user feature permissions (raw JSON string)
  getFeaturePermissions: (userId) => {
    try {
      const row = db.prepare('SELECT feature_permissions FROM users WHERE id = ?').get(userId);
      return row?.feature_permissions || null;
    } catch (err) {
      throw err;
    }
  },

  // Update user feature permissions (JSON string or null to reset to default)
  updateFeaturePermissions: (userId, permissions) => {
    try {
      const value = permissions === null ? null : JSON.stringify(permissions);
      db.prepare('UPDATE users SET feature_permissions = ? WHERE id = ?').run(value, userId);
    } catch (err) {
      throw err;
    }
  },

  // Get user visible agents (raw JSON string)
  getVisibleAgents: (userId) => {
    try {
      const row = db.prepare('SELECT visible_agents FROM users WHERE id = ?').get(userId);
      return row?.visible_agents || null;
    } catch (err) {
      throw err;
    }
  },

  // Update user visible agents (JSON array or null to reset)
  updateVisibleAgents: (userId, agents) => {
    try {
      const value = agents === null ? null : JSON.stringify(agents);
      db.prepare('UPDATE users SET visible_agents = ? WHERE id = ?').run(value, userId);
    } catch (err) {
      throw err;
    }
  }
};

// Verification codes database operations
const verificationDb = {
  // Generate and store verification code
  createCode: (email, type = 'login', ipAddress = null) => {
    try {
      const code = Math.floor(100000 + Math.random() * 900000).toString(); // 6-digit code
      const expiresAt = new Date(Date.now() + 5 * 60 * 1000).toISOString(); // 5 minutes

      const stmt = db.prepare(`
        INSERT INTO verification_codes (email, code, type, expires_at, ip_address)
        VALUES (?, ?, ?, ?, ?)
      `);
      const result = stmt.run(email, code, type, expiresAt, ipAddress);
      return { id: result.lastInsertRowid, code };
    } catch (err) {
      throw err;
    }
  },

  // Verify code and check if valid
  verifyCode: (email, code) => {
    try {
      const now = new Date().toISOString();

      // Find valid, unused code
      const record = db.prepare(`
        SELECT * FROM verification_codes
        WHERE email = ? AND code = ? AND used = 0 AND expires_at > ?
        ORDER BY created_at DESC LIMIT 1
      `).get(email, code, now);

      if (!record) {
        return { valid: false, error: 'invalid_code' };
      }

      if (record.attempts >= 5) {
        return { valid: false, error: 'max_attempts' };
      }

      // Mark as used
      db.prepare('UPDATE verification_codes SET used = 1 WHERE id = ?').run(record.id);

      return { valid: true, type: record.type };
    } catch (err) {
      throw err;
    }
  },

  // Increment attempt count
  incrementAttempts: (email, code) => {
    try {
      db.prepare(`
        UPDATE verification_codes SET attempts = attempts + 1
        WHERE email = ? AND code = ? AND used = 0
      `).run(email, code);
    } catch (err) {
      throw err;
    }
  },

  // Check rate limit for sending codes
  canSendCode: (email) => {
    try {
      const oneMinuteAgo = new Date(Date.now() - 60 * 1000).toISOString();
      const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();

      // Check per-email rate limit (1 per minute)
      const recentByEmail = db.prepare(`
        SELECT COUNT(*) as count FROM verification_codes
        WHERE email = ? AND created_at > ?
      `).get(email, oneMinuteAgo);

      if (recentByEmail.count >= 1) {
        return { allowed: false, error: 'rate_limit_email', waitSeconds: 60 };
      }

      // Check per-email hourly limit (10 per hour)
      const hourlyByEmail = db.prepare(`
        SELECT COUNT(*) as count FROM verification_codes
        WHERE email = ? AND created_at > ?
      `).get(email, oneHourAgo);

      if (hourlyByEmail.count >= 10) {
        return { allowed: false, error: 'rate_limit_hourly' };
      }

      return { allowed: true };
    } catch (err) {
      throw err;
    }
  },

  // Cleanup expired codes
  cleanupExpired: () => {
    try {
      const now = new Date().toISOString();
      const result = db.prepare('DELETE FROM verification_codes WHERE expires_at < ?').run(now);
      return result.changes;
    } catch (err) {
      throw err;
    }
  }
};

// Usage database operations
const usageDb = {
  // Check if a usage record already exists (for deduplication)
  // Uses session_id + model + all token counts to identify duplicates within a time window
  // If session_id is null, uses user_uuid instead for matching
  checkRecordExists: (userUuid, sessionId, model, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, createdAt) => {
    try {
      // Parse the timestamp and create a time window (±5 seconds to handle network delays)
      const timestamp = new Date(createdAt || Date.now());
      const windowStart = new Date(timestamp.getTime() - 5000).toISOString();
      const windowEnd = new Date(timestamp.getTime() + 5000).toISOString();

      let stmt;
      let result;

      if (sessionId) {
        // Use session_id for matching (more precise)
        stmt = db.prepare(`
          SELECT id FROM usage_records
          WHERE session_id = ?
            AND model = ?
            AND input_tokens = ?
            AND output_tokens = ?
            AND cache_read_tokens = ?
            AND cache_creation_tokens = ?
            AND created_at BETWEEN ? AND ?
          LIMIT 1
        `);
        result = stmt.get(
          sessionId, model, inputTokens, outputTokens,
          cacheReadTokens || 0, cacheCreationTokens || 0,
          windowStart, windowEnd
        );
      } else if (userUuid) {
        // Fallback to user_uuid when session_id is not available
        stmt = db.prepare(`
          SELECT id FROM usage_records
          WHERE user_uuid = ?
            AND session_id IS NULL
            AND model = ?
            AND input_tokens = ?
            AND output_tokens = ?
            AND cache_read_tokens = ?
            AND cache_creation_tokens = ?
            AND created_at BETWEEN ? AND ?
          LIMIT 1
        `);
        result = stmt.get(
          userUuid, model, inputTokens, outputTokens,
          cacheReadTokens || 0, cacheCreationTokens || 0,
          windowStart, windowEnd
        );
      } else {
        // No identifier available, cannot deduplicate
        return false;
      }

      return !!result;
    } catch (err) {
      console.error('[usageDb] Error checking record exists:', err);
      return false;
    }
  },

  // Insert a usage record
  insertRecord: (record) => {
    try {
      const stmt = db.prepare(`
        INSERT INTO usage_records (user_uuid, session_id, model, raw_model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, cost_usd, source, created_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      `);
      const result = stmt.run(
        record.user_uuid,
        record.session_id || null,
        record.model,
        record.raw_model || null,
        record.input_tokens || 0,
        record.output_tokens || 0,
        record.cache_read_tokens || 0,
        record.cache_creation_tokens || 0,
        record.cost_usd || 0,
        record.source || 'sdk',
        record.created_at || new Date().toISOString()
      );
      return result.lastInsertRowid;
    } catch (err) {
      throw err;
    }
  },

  // Upsert daily summary
  upsertDailySummary: (summary) => {
    try {
      const stmt = db.prepare(`
        INSERT INTO usage_daily_summary (user_uuid, date, model, total_input_tokens, total_output_tokens, total_cost_usd, session_count, request_count)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        ON CONFLICT(user_uuid, date, model) DO UPDATE SET
          total_input_tokens = total_input_tokens + excluded.total_input_tokens,
          total_output_tokens = total_output_tokens + excluded.total_output_tokens,
          total_cost_usd = total_cost_usd + excluded.total_cost_usd,
          session_count = session_count + excluded.session_count,
          request_count = request_count + excluded.request_count
      `);
      stmt.run(
        summary.user_uuid,
        summary.date,
        summary.model,
        summary.total_input_tokens || 0,
        summary.total_output_tokens || 0,
        summary.total_cost_usd || 0,
        summary.session_count || 0,
        summary.request_count || 0
      );
    } catch (err) {
      throw err;
    }
  },

  // Atomic transaction: insert usage record + upsert daily summary
  recordUsageTransaction: db.transaction((record, summary) => {
    const insertStmt = db.prepare(`
      INSERT INTO usage_records (user_uuid, session_id, model, raw_model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, cost_usd, source, created_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `);
    insertStmt.run(
      record.user_uuid,
      record.session_id || null,
      record.model,
      record.raw_model || null,
      record.input_tokens || 0,
      record.output_tokens || 0,
      record.cache_read_tokens || 0,
      record.cache_creation_tokens || 0,
      record.cost_usd || 0,
      record.source || 'sdk',
      record.created_at || new Date().toISOString()
    );

    const upsertStmt = db.prepare(`
      INSERT INTO usage_daily_summary (user_uuid, date, model, total_input_tokens, total_output_tokens, total_cost_usd, session_count, request_count)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?)
      ON CONFLICT(user_uuid, date, model) DO UPDATE SET
        total_input_tokens = total_input_tokens + excluded.total_input_tokens,
        total_output_tokens = total_output_tokens + excluded.total_output_tokens,
        total_cost_usd = total_cost_usd + excluded.total_cost_usd,
        session_count = session_count + excluded.session_count,
        request_count = request_count + excluded.request_count
    `);
    upsertStmt.run(
      summary.user_uuid,
      summary.date,
      summary.model,
      summary.total_input_tokens || 0,
      summary.total_output_tokens || 0,
      summary.total_cost_usd || 0,
      summary.session_count || 0,
      summary.request_count || 0
    );
  }),

  // Get all users usage summary
  getAllUsersSummary: () => {
    try {
      return db.prepare(`
        SELECT
          uds.user_uuid,
          SUM(uds.total_cost_usd) as total_cost,
          SUM(uds.request_count) as total_requests,
          (SELECT COUNT(DISTINCT ur.session_id) FROM usage_records ur WHERE ur.user_uuid = uds.user_uuid AND ur.session_id IS NOT NULL) as total_sessions,
          MAX(uds.date) as last_active
        FROM usage_daily_summary uds
        GROUP BY uds.user_uuid
        ORDER BY total_cost DESC
      `).all();
    } catch (err) {
      throw err;
    }
  },

  // Get user usage by period
  getUserUsageByPeriod: (userUuid, startDate, endDate) => {
    try {
      return db.prepare(`
        SELECT
          date,
          model,
          total_input_tokens,
          total_output_tokens,
          total_cost_usd,
          session_count,
          request_count
        FROM usage_daily_summary
        WHERE user_uuid = ? AND date >= ? AND date <= ?
        ORDER BY date DESC
      `).all(userUuid, startDate, endDate);
    } catch (err) {
      throw err;
    }
  },

  // Get user total usage
  getUserTotalUsage: (userUuid) => {
    try {
      return db.prepare(`
        SELECT
          SUM(total_cost_usd) as total_cost,
          SUM(total_input_tokens) as total_input_tokens,
          SUM(total_output_tokens) as total_output_tokens,
          SUM(request_count) as total_requests,
          (SELECT COUNT(DISTINCT session_id) FROM usage_records WHERE user_uuid = ? AND session_id IS NOT NULL) as total_sessions
        FROM usage_daily_summary
        WHERE user_uuid = ?
      `).get(userUuid, userUuid);
    } catch (err) {
      throw err;
    }
  },

  // Get model distribution for a user
  getUserModelDistribution: (userUuid, startDate, endDate) => {
    try {
      return db.prepare(`
        SELECT
          COALESCE(raw_model, model) as model,
          model as normalized_model,
          SUM(cost_usd) as cost,
          COUNT(*) as requests
        FROM usage_records
        WHERE user_uuid = ? AND created_at >= ? AND created_at <= ?
          AND COALESCE(raw_model, model) != '<synthetic>'
        GROUP BY COALESCE(raw_model, model)
        ORDER BY cost DESC
      `).all(userUuid, startDate + 'T00:00:00', endDate + 'T23:59:59');
    } catch (err) {
      throw err;
    }
  },

  // Get global dashboard stats
  getDashboardStats: (startDate, endDate) => {
    try {
      const totals = db.prepare(`
        SELECT
          SUM(total_cost_usd) as total_cost,
          SUM(request_count) as total_requests,
          (SELECT COUNT(DISTINCT session_id) FROM usage_records WHERE date(created_at) >= ? AND date(created_at) <= ? AND session_id IS NOT NULL) as total_sessions,
          COUNT(DISTINCT user_uuid) as active_users
        FROM usage_daily_summary
        WHERE date >= ? AND date <= ?
      `).get(startDate, endDate, startDate, endDate);

      const dailyTrend = db.prepare(`
        SELECT
          date,
          SUM(total_cost_usd) as cost,
          SUM(request_count) as requests
        FROM usage_daily_summary
        WHERE date >= ? AND date <= ?
        GROUP BY date
        ORDER BY date ASC
      `).all(startDate, endDate);

      const modelDistribution = db.prepare(`
        SELECT
          COALESCE(raw_model, model) as model,
          model as normalized_model,
          SUM(cost_usd) as cost,
          COUNT(*) as requests
        FROM usage_records
        WHERE created_at >= ? AND created_at <= ?
          AND COALESCE(raw_model, model) != '<synthetic>'
        GROUP BY COALESCE(raw_model, model)
        ORDER BY cost DESC
      `).all(startDate + 'T00:00:00', endDate + 'T23:59:59');

      const topUsers = db.prepare(`
        SELECT
          user_uuid,
          SUM(total_cost_usd) as total_cost,
          SUM(request_count) as total_requests
        FROM usage_daily_summary
        WHERE date >= ? AND date <= ?
        GROUP BY user_uuid
        ORDER BY total_cost DESC
        LIMIT 10
      `).all(startDate, endDate);

      return { totals, dailyTrend, modelDistribution, topUsers };
    } catch (err) {
      throw err;
    }
  },

  // Cleanup old records (older than specified days)
  cleanupOldRecords: (days) => {
    try {
      const cutoffDate = new Date();
      cutoffDate.setDate(cutoffDate.getDate() - days);
      const cutoffStr = cutoffDate.toISOString();

      const result = db.prepare('DELETE FROM usage_records WHERE created_at < ?').run(cutoffStr);
      return result.changes;
    } catch (err) {
      throw err;
    }
  },

  // Get user's daily usage for a specific date
  getUserDailyUsage: (userUuid, date = null) => {
    try {
      const targetDate = date || new Date().toISOString().split('T')[0];
      const result = db.prepare(`
        SELECT
          SUM(total_cost_usd) as daily_cost
        FROM usage_daily_summary
        WHERE user_uuid = ? AND date = ?
      `).get(userUuid, targetDate);
      return result?.daily_cost || 0;
    } catch (err) {
      throw err;
    }
  },

  // Check if user has exceeded spending limits
  checkUserLimits: (userUuid) => {
    try {
      // Get user's limits
      const user = db.prepare('SELECT total_limit_usd, daily_limit_usd FROM users WHERE uuid = ?').get(userUuid);
      if (!user) {
        return { allowed: true };
      }

      // Get total usage
      const totalUsage = db.prepare(`
        SELECT SUM(total_cost_usd) as total_cost
        FROM usage_daily_summary
        WHERE user_uuid = ?
      `).get(userUuid);

      // Get daily usage
      const today = new Date().toISOString().split('T')[0];
      const dailyUsage = db.prepare(`
        SELECT SUM(total_cost_usd) as daily_cost
        FROM usage_daily_summary
        WHERE user_uuid = ? AND date = ?
      `).get(userUuid, today);

      const totalCost = totalUsage?.total_cost || 0;
      const dailyCost = dailyUsage?.daily_cost || 0;

      // Check total limit first
      if (user.total_limit_usd !== null && totalCost >= user.total_limit_usd) {
        return {
          allowed: false,
          reason: 'total_limit_exceeded',
          limit: user.total_limit_usd,
          current: totalCost
        };
      }

      // Check daily limit
      if (user.daily_limit_usd !== null && dailyCost >= user.daily_limit_usd) {
        return {
          allowed: false,
          reason: 'daily_limit_exceeded',
          limit: user.daily_limit_usd,
          current: dailyCost
        };
      }

      return {
        allowed: true,
        totalCost,
        dailyCost,
        totalLimit: user.total_limit_usd,
        dailyLimit: user.daily_limit_usd
      };
    } catch (err) {
      throw err;
    }
  },

  // 删除用户所有用量记录（用户删除时调用）
  deleteByUserUuid: (userUuid) => {
    db.prepare('DELETE FROM usage_records WHERE user_uuid = ?').run(userUuid);
    db.prepare('DELETE FROM usage_daily_summary WHERE user_uuid = ?').run(userUuid);
  }
};

// Email domain whitelist database operations
const domainWhitelistDb = {
  // Get all whitelisted domains
  getAllDomains: () => {
    try {
      return db.prepare('SELECT * FROM email_domain_whitelist ORDER BY domain ASC').all();
    } catch (err) {
      throw err;
    }
  },

  // Add a domain to whitelist
  addDomain: (domain, createdBy = null) => {
    try {
      const normalizedDomain = domain.toLowerCase().trim();
      const stmt = db.prepare('INSERT INTO email_domain_whitelist (domain, created_by) VALUES (?, ?)');
      const result = stmt.run(normalizedDomain, createdBy);
      return { id: result.lastInsertRowid, domain: normalizedDomain };
    } catch (err) {
      if (err.code === 'SQLITE_CONSTRAINT_UNIQUE') {
        throw new Error('域名已存在');
      }
      throw err;
    }
  },

  // Remove a domain from whitelist
  removeDomain: (id) => {
    try {
      const result = db.prepare('DELETE FROM email_domain_whitelist WHERE id = ?').run(id);
      return result.changes > 0;
    } catch (err) {
      throw err;
    }
  },

  // Check if email domain is allowed
  isEmailAllowed: (email) => {
    try {
      // First check if whitelist is empty (allow all if no restrictions)
      const count = db.prepare('SELECT COUNT(*) as count FROM email_domain_whitelist').get();
      if (count.count === 0) {
        return true; // No whitelist configured, allow all
      }

      // Extract domain from email
      const domain = email.toLowerCase().split('@')[1];
      if (!domain) {
        return false;
      }

      // Check if domain is in whitelist
      const row = db.prepare('SELECT id FROM email_domain_whitelist WHERE domain = ?').get(domain);
      return !!row;
    } catch (err) {
      throw err;
    }
  },

  // Get whitelist count
  getCount: () => {
    try {
      const row = db.prepare('SELECT COUNT(*) as count FROM email_domain_whitelist').get();
      return row.count;
    } catch (err) {
      throw err;
    }
  }
};

const settingsDb = {
  get: (key) => {
    const row = db.prepare('SELECT value FROM system_settings WHERE key = ?').get(key);
    return row ? row.value : null;
  },

  set: (key, value, updatedBy = null) => {
    db.prepare(`
      INSERT INTO system_settings (key, value, updated_at, updated_by)
      VALUES (?, ?, CURRENT_TIMESTAMP, ?)
      ON CONFLICT(key) DO UPDATE SET
        value = excluded.value,
        updated_at = excluded.updated_at,
        updated_by = excluded.updated_by
    `).run(key, value, updatedBy);
  },

  delete: (key) => {
    db.prepare('DELETE FROM system_settings WHERE key = ?').run(key);
  }
};

const feishuDb = {
  getBinding: (feishuOpenId) => {
    return db.prepare('SELECT * FROM feishu_bindings WHERE feishu_open_id = ?').get(feishuOpenId) || null;
  },

  createBinding: (feishuOpenId, userUuid) => {
    db.prepare(`
      INSERT INTO feishu_bindings (feishu_open_id, user_uuid)
      VALUES (?, ?)
      ON CONFLICT(feishu_open_id) DO UPDATE SET user_uuid = excluded.user_uuid
    `).run(feishuOpenId, userUuid);
  },

  removeBinding: (feishuOpenId) => {
    db.prepare('DELETE FROM feishu_bindings WHERE feishu_open_id = ?').run(feishuOpenId);
  },

  getBindingByUserUuid: (userUuid) => {
    return db.prepare('SELECT * FROM feishu_bindings WHERE user_uuid = ?').get(userUuid) || null;
  },

  getSessionState: (feishuOpenId) => {
    return db.prepare('SELECT * FROM feishu_session_state WHERE feishu_open_id = ?').get(feishuOpenId) || null;
  },

  updateSessionState: (feishuOpenId, updates) => {
    const state = db.prepare('SELECT * FROM feishu_session_state WHERE feishu_open_id = ?').get(feishuOpenId);
    if (state) {
      const fields = [];
      const values = [];
      if ('claude_session_id' in updates) { fields.push('claude_session_id = ?'); values.push(updates.claude_session_id); }
      if ('cwd' in updates) { fields.push('cwd = ?'); values.push(updates.cwd); }
      if ('permission_mode' in updates) { fields.push('permission_mode = ?'); values.push(updates.permission_mode); }
      if ('claude_model' in updates) { fields.push('claude_model = ?'); values.push(updates.claude_model); }
      if ('chat_id' in updates) { fields.push('chat_id = ?'); values.push(updates.chat_id); }
      fields.push('updated_at = CURRENT_TIMESTAMP');
      values.push(feishuOpenId);
      db.prepare(`UPDATE feishu_session_state SET ${fields.join(', ')} WHERE feishu_open_id = ?`).run(...values);
    } else {
      db.prepare(`
        INSERT INTO feishu_session_state (feishu_open_id, claude_session_id, cwd, permission_mode, claude_model, chat_id)
        VALUES (?, ?, ?, ?, ?, ?)
      `).run(
        feishuOpenId,
        updates.claude_session_id ?? null,
        updates.cwd ?? '',
        updates.permission_mode ?? 'default',
        updates.claude_model ?? 'sonnet',
        updates.chat_id ?? null
      );
    }
  },

  clearSession: (feishuOpenId) => {
    db.prepare('UPDATE feishu_session_state SET claude_session_id = NULL, updated_at = CURRENT_TIMESTAMP WHERE feishu_open_id = ?').run(feishuOpenId);
  },

  // 消息去重：检查消息是否已处理
  isProcessed: (messageId) => {
    const row = db.prepare('SELECT 1 FROM feishu_processed_messages WHERE message_id = ?').get(messageId);
    return !!row;
  },

  // 消息去重：标记消息为已处理，并清理 48 小时前的旧记录
  markProcessed: (messageId) => {
    db.prepare(`
      INSERT OR IGNORE INTO feishu_processed_messages (message_id) VALUES (?)
    `).run(messageId);
    // 清理 48 小时前的记录，防止表无限增长
    db.prepare(`
      DELETE FROM feishu_processed_messages
      WHERE processed_at < datetime('now', '-48 hours')
    `).run();
  },

  // 删除用户所有飞书相关数据（用户删除时调用）
  deleteByUserUuid: (userUuid) => {
    // 先查出所有绑定的 feishu_open_id，清理 session_state
    const bindings = db.prepare('SELECT feishu_open_id FROM feishu_bindings WHERE user_uuid = ?').all(userUuid);
    for (const b of bindings) {
      db.prepare('DELETE FROM feishu_session_state WHERE feishu_open_id = ?').run(b.feishu_open_id);
    }
    db.prepare('DELETE FROM feishu_bindings WHERE user_uuid = ?').run(userUuid);
  }
};

// Image database operations (聊天图片持久化)
const imageDb = {
  insertImage: ({ upload_batch_id, user_uuid, original_name, file_hash, file_ext, file_size, mime_type }) => {
    const result = db.prepare(`
      INSERT INTO message_images (upload_batch_id, user_uuid, original_name, file_hash, file_ext, file_size, mime_type)
      VALUES (?, ?, ?, ?, ?, ?, ?)
    `).run(upload_batch_id, user_uuid, original_name, file_hash, file_ext, file_size, mime_type);
    return result.lastInsertRowid;
  },

  associateBatch: (uploadBatchId, sessionId, messageContent) => {
    db.prepare(`
      UPDATE message_images SET session_id = ?, message_content = ? WHERE upload_batch_id = ?
    `).run(sessionId, messageContent || null, uploadBatchId);
  },

  getImagesBySession: (sessionId) => {
    return db.prepare(`
      SELECT id, upload_batch_id, original_name, file_hash, file_ext, mime_type, message_content, created_at
      FROM message_images WHERE session_id = ? ORDER BY created_at ASC
    `).all(sessionId);
  },

  getImageById: (imageId) => {
    return db.prepare(`
      SELECT id, user_uuid, original_name, file_hash, file_ext, mime_type, created_at
      FROM message_images WHERE id = ?
    `).get(imageId) || null;
  },

  getExpiredImages: (days) => {
    return db.prepare(`
      SELECT id, user_uuid, file_hash, file_ext
      FROM message_images WHERE created_at < datetime('now', '-' || ? || ' days')
    `).all(days);
  },

  deleteImagesByIds: (ids) => {
    if (!ids.length) return;
    const placeholders = ids.map(() => '?').join(',');
    db.prepare(`DELETE FROM message_images WHERE id IN (${placeholders})`).run(...ids);
  },

  deleteOrphanedImages: (hours) => {
    const rows = db.prepare(`
      SELECT id, user_uuid, file_hash, file_ext
      FROM message_images WHERE session_id IS NULL AND created_at < datetime('now', '-' || ? || ' hours')
    `).all(hours);
    if (rows.length) {
      const ids = rows.map(r => r.id);
      const placeholders = ids.map(() => '?').join(',');
      db.prepare(`DELETE FROM message_images WHERE id IN (${placeholders})`).run(...ids);
    }
    return rows;
  },

  countByHash: (userUuid, fileHash, excludeIds) => {
    if (!excludeIds.length) {
      const row = db.prepare(`
        SELECT COUNT(*) as count FROM message_images WHERE user_uuid = ? AND file_hash = ?
      `).get(userUuid, fileHash);
      return row.count;
    }
    const placeholders = excludeIds.map(() => '?').join(',');
    const row = db.prepare(`
      SELECT COUNT(*) as count FROM message_images
      WHERE user_uuid = ? AND file_hash = ? AND id NOT IN (${placeholders})
    `).get(userUuid, fileHash, ...excludeIds);
    return row.count;
  },

  // 查询用户所有图片记录（用于删除文件）
  getImagesByUserUuid: (userUuid) => {
    return db.prepare('SELECT file_hash, file_ext FROM message_images WHERE user_uuid = ?').all(userUuid);
  },

  // 删除用户所有图片 DB 记录（用户删除时调用）
  deleteByUserUuid: (userUuid) => {
    db.prepare('DELETE FROM message_images WHERE user_uuid = ?').run(userUuid);
  }
};

// Agent submissions database operations
const agentSubmissionDb = {
  create: ({ userId, agentName, displayName, description, zipPath, updateNotes }) => {
    const result = db.prepare(`
      INSERT INTO agent_submissions (user_id, agent_name, display_name, description, zip_path, update_notes)
      VALUES (?, ?, ?, ?, ?, ?)
    `).run(userId, agentName, displayName, description || null, zipPath, updateNotes || null);
    return result.lastInsertRowid;
  },

  listAll: (status = null) => {
    if (status) {
      return db.prepare(`
        SELECT s.*, u.username, u.email
        FROM agent_submissions s
        LEFT JOIN users u ON s.user_id = u.id
        WHERE s.status = ?
        ORDER BY s.submitted_at DESC
      `).all(status);
    }
    return db.prepare(`
      SELECT s.*, u.username, u.email
      FROM agent_submissions s
      LEFT JOIN users u ON s.user_id = u.id
      ORDER BY s.submitted_at DESC
    `).all();
  },

  listByUser: (userId) => {
    return db.prepare(`
      SELECT * FROM agent_submissions WHERE user_id = ? ORDER BY submitted_at DESC
    `).all(userId);
  },

  getById: (id) => {
    return db.prepare(`
      SELECT s.*, u.username, u.email
      FROM agent_submissions s
      LEFT JOIN users u ON s.user_id = u.id
      WHERE s.id = ?
    `).get(id) || null;
  },

  updateStatus: (id, status, { version = null, rejectReason = null, reviewedBy = null } = {}) => {
    db.prepare(`
      UPDATE agent_submissions
      SET status = ?, version = ?, reject_reason = ?, reviewed_by = ?, reviewed_at = CURRENT_TIMESTAMP
      WHERE id = ?
    `).run(status, version, rejectReason, reviewedBy, id);
  },

  // Get approved agent names by user (for auto-visibility)
  getApprovedAgentNamesByUser: (userId) => {
    return db.prepare(`
      SELECT DISTINCT agent_name FROM agent_submissions WHERE user_id = ? AND status = 'approved'
    `).all(userId).map(row => row.agent_name);
  },

  // 删除用户所有 Agent 提交记录（用户删除时调用）
  deleteByUserId: (userId) => {
    db.prepare('DELETE FROM agent_submissions WHERE user_id = ?').run(userId);
  }
};

// Cron tasks database operations
const cronDb = {
  insert: (task) => {
    db.prepare(`
      INSERT INTO cron_tasks (id, cron_expr, prompt, recurring, user_uuid, session_id,
        project_path, session_mode, channel, feishu_open_id, chat_id, webhook_url, created_at, last_fired_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `).run(
      task.id, task.cronExpr, task.prompt, task.recurring ? 1 : 0,
      task.userUuid, task.sessionId || null, task.projectPath || null,
      task.sessionMode || 'new_session', task.channel ?? '',
      task.feishuOpenId || null, task.chatId || null, task.webhookUrl || null,
      task.createdAt, task.lastFiredAt || null
    );
  },

  delete: (id, userUuid) => {
    const result = db.prepare('DELETE FROM cron_tasks WHERE id = ? AND user_uuid = ?').run(id, userUuid);
    return result.changes > 0;
  },

  listByUser: (userUuid) => {
    return db.prepare('SELECT * FROM cron_tasks WHERE user_uuid = ? ORDER BY created_at ASC').all(userUuid)
      .map(row => ({
        id: row.id, cronExpr: row.cron_expr, prompt: row.prompt,
        recurring: row.recurring === 1, userUuid: row.user_uuid,
        sessionId: row.session_id, projectPath: row.project_path,
        sessionMode: row.session_mode, channel: row.channel,
        feishuOpenId: row.feishu_open_id, chatId: row.chat_id,
        webhookUrl: row.webhook_url, createdAt: row.created_at, lastFiredAt: row.last_fired_at,
        isPaused: row.is_paused === 1,
      }));
  },

  getAll: () => {
    return db.prepare('SELECT * FROM cron_tasks ORDER BY created_at ASC').all()
      .map(row => ({
        id: row.id, cronExpr: row.cron_expr, prompt: row.prompt,
        recurring: row.recurring === 1, userUuid: row.user_uuid,
        sessionId: row.session_id, projectPath: row.project_path,
        sessionMode: row.session_mode, channel: row.channel,
        feishuOpenId: row.feishu_open_id, chatId: row.chat_id,
        webhookUrl: row.webhook_url, createdAt: row.created_at, lastFiredAt: row.last_fired_at,
        isPaused: row.is_paused === 1,
      }));
  },

  updateLastFired: (id, ts) => {
    db.prepare('UPDATE cron_tasks SET last_fired_at = ? WHERE id = ?').run(ts, id);
  },

  setPaused: (id, userUuid, isPaused) => {
    const result = db.prepare('UPDATE cron_tasks SET is_paused = ? WHERE id = ? AND user_uuid = ?')
      .run(isPaused ? 1 : 0, id, userUuid);
    return result.changes > 0;
  },

  update: (id, userUuid, patch) => {
    const result = db.prepare(`
      UPDATE cron_tasks
      SET cron_expr = ?, prompt = ?, recurring = ?, channel = ?,
          webhook_url = ?, feishu_open_id = ?, chat_id = ?, project_path = ?
      WHERE id = ? AND user_uuid = ?
    `).run(
      patch.cronExpr, patch.prompt, patch.recurring ? 1 : 0,
      patch.channel ?? '', patch.webhookUrl || null,
      patch.feishuOpenId || null, patch.chatId || null,
      patch.projectPath || null,
      id, userUuid
    );
    return result.changes > 0;
  },
};

// Share database operations
const shareDb = {
  getShare: (userUuid, sessionId) => {
    return db.prepare('SELECT * FROM session_shares WHERE user_uuid = ? AND session_id = ?').get(userUuid, sessionId);
  },

  getShareBySessionId: (sessionId) => {
    return db.prepare('SELECT * FROM session_shares WHERE session_id = ?').get(sessionId);
  },

  upsertShare: (data) => {
    const now = new Date().toISOString();
    db.prepare(`
      INSERT INTO session_shares (user_uuid, session_id, project_name, share_mode, status, session_title, created_at, updated_at)
      VALUES (?, ?, ?, ?, 'active', ?, ?, ?)
      ON CONFLICT(user_uuid, session_id) DO UPDATE SET
        share_mode = excluded.share_mode,
        status = 'active',
        session_title = excluded.session_title,
        updated_at = excluded.updated_at
    `).run(data.userUuid, data.sessionId, data.projectName, data.shareMode, data.sessionTitle || null, now, now);
  },

  stopShare: (userUuid, sessionId) => {
    const now = new Date().toISOString();
    db.prepare('UPDATE session_shares SET status = ?, updated_at = ? WHERE user_uuid = ? AND session_id = ?')
      .run('stopped', now, userUuid, sessionId);
  },

  updateToStatic: (userUuid, sessionId) => {
    const now = new Date().toISOString();
    db.prepare('UPDATE session_shares SET share_mode = ?, updated_at = ? WHERE user_uuid = ? AND session_id = ?')
      .run('static', now, userUuid, sessionId);
  },
};

export {
  db,
  initializeDatabase,
  userDb,
  usageDb,
  verificationDb,
  domainWhitelistDb,
  settingsDb,
  feishuDb,
  imageDb,
  agentSubmissionDb,
  cronDb,
  shareDb
};