const crypto = require('crypto');
const bcrypt = require('bcryptjs');
const { getDb } = require('../db/database');

const COOKIE_NAME = 'bt_session';
const SESSION_DAYS = 7;

function envFlag(name) {
  const value = process.env[name];
  if (value === undefined) return null;
  return ['1', 'true', 'yes', 'on'].includes(String(value).toLowerCase());
}

function requestLooksHttps(req) {
  if (!req) return false;
  if (req.secure) return true;
  const proto = req.get?.('x-forwarded-proto') || req.headers?.['x-forwarded-proto'];
  return String(proto || '').split(',').map(s => s.trim()).includes('https');
}

/**
 * Build session-cookie options.
 *
 * COOKIE_SECURE=true/false is explicit. HTTPS=true keeps the old deployment knob.
 * Otherwise, mark cookies Secure only when the current request appears to be HTTPS.
 */
function cookieOpts(req) {
  const cookieSecure = envFlag('COOKIE_SECURE');
  const httpsSecure  = envFlag('HTTPS');
  const secure = cookieSecure !== null
    ? cookieSecure
    : httpsSecure !== null
    ? httpsSecure
    : requestLooksHttps(req);

  return {
    httpOnly: true,
    sameSite: 'strict',
    secure,
    maxAge: SESSION_DAYS * 86400 * 1000,
    path: '/',
  };
}

async function login(username, password) {
  const db = getDb();
  const user = db.prepare('SELECT * FROM users WHERE username = ?').get(username);
  if (!user) return null;
  if (user.active === 0) return null;

  // Reject OIDC-only accounts from local login
  if (user.auth_provider && user.auth_provider !== 'local') {
    return null;
  }

  const valid = await bcrypt.compare(password, user.password_hash);
  if (!valid) return null;

  // Clean up expired sessions for this user before creating new session
  try {
    db.prepare("DELETE FROM sessions WHERE user_id = ? AND expires_at < datetime('now')").run(user.id);
  } catch (err) {
    console.error('[cleanup-error] Failed to cleanup user expired sessions:', err.message);
  }

  const sessionId = crypto.randomUUID();
  const expiresAt = new Date(Date.now() + SESSION_DAYS * 86400000)
    .toISOString().slice(0, 19).replace('T', ' ');

  db.prepare('INSERT INTO sessions (id, user_id, expires_at) VALUES (?, ?, ?)')
    .run(sessionId, user.id, expiresAt);

  // Update last_login_at if column exists (added in v0.17 migration)
  try {
    db.prepare("UPDATE users SET last_login_at = datetime('now') WHERE id = ?").run(user.id);
  } catch { /* column may not exist on older schemas */ }

  return { sessionId, user: publicUser(user) };
}

async function createSession(userId) {
  const db = getDb();
  const user = db.prepare('SELECT * FROM users WHERE id = ?').get(userId);
  if (!user) return null;
  if (user.active === 0) return null;

  // Clean up expired sessions for this user before creating new session
  try {
    db.prepare("DELETE FROM sessions WHERE user_id = ? AND expires_at < datetime('now')").run(userId);
  } catch (err) {
    console.error('[cleanup-error] Failed to cleanup user expired sessions:', err.message);
  }

  const sessionId = crypto.randomUUID();
  const expiresAt = new Date(Date.now() + SESSION_DAYS * 86400000)
    .toISOString().slice(0, 19).replace('T', ' ');

  db.prepare('INSERT INTO sessions (id, user_id, expires_at) VALUES (?, ?, ?)')
    .run(sessionId, user.id, expiresAt);

  return { sessionId, user: publicUser(user) };
}

function logout(sessionId) {
  if (!sessionId) return;
  getDb().prepare('DELETE FROM sessions WHERE id = ?').run(sessionId);
}

/**
 * Regenerate session ID for security (e.g., on privilege escalation).
 * This invalidates the old session and creates a new one with the same user.
 */
function rotateSessionId(oldSessionId, userId) {
  if (!oldSessionId || !userId) return null;
  
  const db = getDb();
  
  // Verify the old session belongs to the user and is valid
  const existingSession = db.prepare('SELECT user_id FROM sessions WHERE id = ? AND expires_at > datetime(\'now\')').get(oldSessionId);
  if (!existingSession || existingSession.user_id !== userId) {
    return null;
  }
  
  // Generate new session ID
  const newSessionId = crypto.randomUUID();
  const expiresAt = new Date(Date.now() + SESSION_DAYS * 86400000)
    .toISOString().slice(0, 19).replace('T', ' ');
  
  // Delete old session and create new one in a transaction
  db.prepare('BEGIN').run();
  try {
    db.prepare('DELETE FROM sessions WHERE id = ?').run(oldSessionId);
    db.prepare('INSERT INTO sessions (id, user_id, expires_at) VALUES (?, ?, ?)')
      .run(newSessionId, userId, expiresAt);
    db.prepare('COMMIT').run();
    
    return newSessionId;
  } catch (err) {
    db.prepare('ROLLBACK').run();
    throw err;
  }
}

function getSessionUser(sessionId) {
  if (!sessionId) return null;
  const row = getDb().prepare(`
    SELECT u.id, u.username, u.display_name, u.role, u.must_change_password, u.first_login,
           u.active, u.is_default_admin
    FROM sessions s
    JOIN users u ON u.id = s.user_id
    WHERE s.id = ? AND s.expires_at > datetime('now') AND u.active = 1
  `).get(sessionId);
  return row || null;
}

async function hashPassword(password) {
  return bcrypt.hash(password, 12);
}

function publicUser(u) {
  return {
    id: u.id,
    username: u.username,
    display_name: u.display_name || null,
    role: u.role,
    active: u.active !== 0,
    is_default_admin: !!u.is_default_admin,
    must_change_password: !!u.must_change_password,
    first_login: !!u.first_login,
  };
}

// Prune expired sessions — called by daily worker
function pruneExpiredSessions() {
  const result = getDb().prepare("DELETE FROM sessions WHERE expires_at <= datetime('now')").run();
  console.log(`[cleanup] Purged ${result.changes} expired sessions`);
  return result;
}

module.exports = { login, logout, createSession, getSessionUser, hashPassword, publicUser, pruneExpiredSessions, cookieOpts, COOKIE_NAME, SESSION_DAYS, rotateSessionId };
