const Database = require('better-sqlite3');
const path = require('path');

let db;

function initDB(dbPath) {
  db = new Database(dbPath || path.join(__dirname, 'skeet_server.db'));
  db.pragma('journal_mode = WAL');
  db.pragma('foreign_keys = ON');

  db.exec(`
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      username TEXT UNIQUE NOT NULL,
      password_hash TEXT NOT NULL,
      role TEXT NOT NULL DEFAULT 'user',
      created_at TEXT NOT NULL DEFAULT (datetime('now')),
      expires_at TEXT,
      is_active INTEGER NOT NULL DEFAULT 1
    );

    CREATE TABLE IF NOT EXISTS devices (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      user_id INTEGER NOT NULL,
      display_name TEXT NOT NULL,
      cpu_hash TEXT NOT NULL,
      board_hash TEXT NOT NULL,
      disk_hash TEXT NOT NULL,
      mac_hash TEXT NOT NULL,
      last_login TEXT,
      last_ip TEXT,
      created_at TEXT NOT NULL DEFAULT (datetime('now')),
      FOREIGN KEY (user_id) REFERENCES users(id)
    );

    CREATE TABLE IF NOT EXISTS refresh_tokens (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      token_hash TEXT UNIQUE NOT NULL,
      user_id INTEGER NOT NULL,
      device_id INTEGER,
      hwid_hash TEXT NOT NULL,
      created_at TEXT NOT NULL DEFAULT (datetime('now')),
      expires_at TEXT NOT NULL,
      revoked INTEGER NOT NULL DEFAULT 0,
      FOREIGN KEY (user_id) REFERENCES users(id)
    );

    CREATE TABLE IF NOT EXISTS login_log (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      user_id INTEGER,
      device_id INTEGER,
      ip_address TEXT,
      user_agent TEXT,
      hwid_match_rate REAL,
      success INTEGER NOT NULL,
      reason TEXT,
      attempted_at TEXT NOT NULL DEFAULT (datetime('now'))
    );

    CREATE TABLE IF NOT EXISTS rate_limits (
      key TEXT NOT NULL,
      action TEXT NOT NULL,
      count INTEGER NOT NULL DEFAULT 1,
      window_start TEXT NOT NULL,
      PRIMARY KEY (key, action)
    );
  `);

  return db;
}

function getDB() {
  if (!db) throw new Error('Database not initialized. Call initDB() first.');
  return db;
}

// ─── Users ────────────────────────────────────────────

function getUserByUsername(username) {
  return getDB().prepare('SELECT * FROM users WHERE username = ?').get(username);
}

function getUserById(id) {
  return getDB().prepare('SELECT * FROM users WHERE id = ?').get(id);
}

function createUser(username, passwordHash, role = 'user', expiresAt = null) {
  return getDB().prepare(
    'INSERT INTO users (username, password_hash, role, expires_at) VALUES (?, ?, ?, ?)'
  ).run(username, passwordHash, role, expiresAt);
}

// ─── Devices ──────────────────────────────────────────

function findMatchingDevice(userId, cpuHash, boardHash, diskHash, macHash) {
  const devices = getDB().prepare(
    'SELECT * FROM devices WHERE user_id = ?'
  ).all(userId);
  return devices;
}

function createDevice(userId, displayName, cpuHash, boardHash, diskHash, macHash, ip) {
  return getDB().prepare(
    `INSERT INTO devices (user_id, display_name, cpu_hash, board_hash, disk_hash, mac_hash, last_login, last_ip)
     VALUES (?, ?, ?, ?, ?, ?, datetime('now'), ?)`
  ).run(userId, displayName, cpuHash, boardHash, diskHash, macHash, ip);
}

function updateDeviceLogin(deviceId, ip) {
  getDB().prepare(
    'UPDATE devices SET last_login = datetime(\'now\'), last_ip = ? WHERE id = ?'
  ).run(ip, deviceId);
}

// ─── Refresh Tokens ───────────────────────────────────

function saveRefreshToken(tokenHash, userId, deviceId, hwidHash, expiresAt) {
  return getDB().prepare(
    'INSERT INTO refresh_tokens (token_hash, user_id, device_id, hwid_hash, expires_at) VALUES (?, ?, ?, ?, ?)'
  ).run(tokenHash, userId, deviceId, hwidHash, expiresAt);
}

function findRefreshToken(tokenHash) {
  return getDB().prepare(
    'SELECT * FROM refresh_tokens WHERE token_hash = ?'
  ).get(tokenHash);
}

function revokeRefreshToken(tokenHash) {
  getDB().prepare(
    'UPDATE refresh_tokens SET revoked = 1 WHERE token_hash = ?'
  ).run(tokenHash);
}

// ─── Login Log ────────────────────────────────────────

function logLogin(userId, deviceId, ip, userAgent, hwidMatchRate, success, reason) {
  getDB().prepare(
    `INSERT INTO login_log (user_id, device_id, ip_address, user_agent, hwid_match_rate, success, reason)
     VALUES (?, ?, ?, ?, ?, ?, ?)`
  ).run(userId, deviceId, ip, userAgent, hwidMatchRate, success ? 1 : 0, reason);
}

function countRecentFailures(username, windowMinutes = 10) {
  const row = getDB().prepare(
    `SELECT COUNT(*) as cnt FROM login_log
     JOIN users ON login_log.user_id = users.id
     WHERE users.username = ? AND login_log.success = 0
     AND login_log.attempted_at > datetime('now', '-' || ? || ' minutes')`
  ).get(username, windowMinutes);
  return row ? row.cnt : 0;
}

// ─── Rate Limits ──────────────────────────────────────

function checkRateLimit(key, action, maxCount, windowSeconds) {
  const now = new Date().toISOString();
  const db = getDB();

  // UPSERT — single row per (key, action)
  db.prepare(
    `INSERT INTO rate_limits (key, action, count, window_start)
     VALUES (?, ?, 1, ?)
     ON CONFLICT(key, action) DO UPDATE SET
       count = CASE
         WHEN (unixepoch(?) - unixepoch(window_start)) > ? THEN 1
         ELSE count + 1
       END,
       window_start = CASE
         WHEN (unixepoch(?) - unixepoch(window_start)) > ? THEN ?
         ELSE window_start
       END`
  ).run(key, action, now, now, windowSeconds, now, windowSeconds, now);

  const row = db.prepare(
    'SELECT count, window_start FROM rate_limits WHERE key = ? AND action = ?'
  ).get(key, action);

  if (!row) return { allowed: true, remaining: maxCount };

  const windowAge = (Date.now() - new Date(row.window_start + 'Z').getTime()) / 1000;
  if (windowAge > windowSeconds) return { allowed: true, remaining: maxCount };

  return {
    allowed: row.count <= maxCount,
    remaining: Math.max(0, maxCount - row.count),
    retryAfter: row.count > maxCount ? Math.ceil(windowSeconds - windowAge) : 0
  };
}

module.exports = {
  initDB, getDB,
  getUserByUsername, getUserById, createUser,
  findMatchingDevice, createDevice, updateDeviceLogin,
  saveRefreshToken, findRefreshToken, revokeRefreshToken,
  logLogin, countRecentFailures,
  checkRateLimit
};
