import { db } from '../../../lib/db.mjs';
import { createHash, randomBytes, scryptSync, timingSafeEqual } from 'crypto';

// --- Schema ---
db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    email TEXT UNIQUE NOT NULL,
    name TEXT NOT NULL DEFAULT '',
    password_hash TEXT,
    role TEXT NOT NULL DEFAULT 'editor',
    active INTEGER NOT NULL DEFAULT 1,
    avatar_color TEXT,
    last_active_at TEXT,
    created_at TEXT DEFAULT (datetime('now')),
    invited_by INTEGER,
    FOREIGN KEY (invited_by) REFERENCES users(id)
  );

  CREATE TABLE IF NOT EXISTS sessions (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER NOT NULL,
    token_hash TEXT UNIQUE NOT NULL,
    ip TEXT,
    user_agent TEXT,
    expires_at TEXT NOT NULL,
    created_at TEXT DEFAULT (datetime('now')),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
  );

  CREATE TABLE IF NOT EXISTS auth_tokens (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    email TEXT NOT NULL,
    token_hash TEXT UNIQUE NOT NULL,
    type TEXT NOT NULL DEFAULT 'login',
    role TEXT DEFAULT 'editor',
    used INTEGER NOT NULL DEFAULT 0,
    expires_at TEXT NOT NULL,
    created_by INTEGER,
    created_at TEXT DEFAULT (datetime('now')),
    FOREIGN KEY (created_by) REFERENCES users(id)
  );

  CREATE TABLE IF NOT EXISTS audit_log (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER,
    user_email TEXT,
    action TEXT NOT NULL,
    target_type TEXT,
    target_id TEXT,
    details TEXT,
    ip TEXT,
    created_at TEXT DEFAULT (datetime('now'))
  );

  CREATE TABLE IF NOT EXISTS login_history (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER NOT NULL,
    ip TEXT,
    user_agent TEXT,
    success INTEGER NOT NULL DEFAULT 1,
    created_at TEXT DEFAULT (datetime('now')),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
  );
`);

// Migration: add password_hash column if missing
try { db.exec("ALTER TABLE users ADD COLUMN password_hash TEXT"); } catch {}
try { db.exec("ALTER TABLE users ADD COLUMN failed_login_count INTEGER NOT NULL DEFAULT 0"); } catch {}
try { db.exec("ALTER TABLE users ADD COLUMN locked_until TEXT"); } catch {}
try { db.exec("ALTER TABLE users ADD COLUMN password_changed_at TEXT"); } catch {}
try { db.exec("ALTER TABLE sessions ADD COLUMN last_seen_at TEXT"); } catch {}
try { db.exec("ALTER TABLE sessions ADD COLUMN revoked_at TEXT"); } catch {}
try { db.exec("UPDATE sessions SET last_seen_at = COALESCE(last_seen_at, created_at)"); } catch {}

const AVATAR_COLORS = [
  '#4a7c59', '#2d6a9f', '#8b5a3c', '#6b4c8a', '#c4713b',
  '#3a8c7a', '#7c4d6e', '#5a7c3a', '#8c5a5a', '#3a6b8c',
];

const SESSION_DURATION_MS = Math.max(1, Number(process.env.VENDOO_SESSION_DAYS || 3)) * 24 * 60 * 60 * 1000;
const SESSION_IDLE_MS = Math.max(10, Number(process.env.VENDOO_SESSION_IDLE_MINUTES || 120)) * 60 * 1000;
const SESSION_TOUCH_INTERVAL_MS = 60 * 1000;
const LOGIN_TOKEN_EXPIRY_MIN = 15;
const INVITE_TOKEN_EXPIRY_HOURS = 48;

function hashToken(token) {
  return createHash('sha256').update(token).digest('hex');
}

function generateToken() {
  return randomBytes(32).toString('hex');
}

function randomAvatarColor() {
  return AVATAR_COLORS[Math.floor(Math.random() * AVATAR_COLORS.length)];
}

// --- Password Hashing (scrypt) ---
export function hashPassword(password) {
  const salt = randomBytes(16).toString('hex');
  const hash = scryptSync(password, salt, 64).toString('hex');
  return `${salt}:${hash}`;
}

export function verifyPassword(password, storedHash) {
  if (!storedHash) return false;
  const [salt, hash] = storedHash.split(':');
  if (!salt || !hash) return false;
  const hashBuf = Buffer.from(hash, 'hex');
  const supplied = scryptSync(password, salt, 64);
  return timingSafeEqual(hashBuf, supplied);
}

// --- Users ---
export function createUser(email, name, role = 'editor', invitedBy = null, password = null) {
  role = ['admin', 'manager', 'editor', 'publisher', 'viewer'].includes(role) ? role : 'editor';
  const existing = db.prepare('SELECT id FROM users WHERE email = ?').get(email);
  if (existing) throw new Error('User existiert bereits');
  const color = randomAvatarColor();
  const pwHash = password ? hashPassword(password) : null;
  const r = db.prepare('INSERT INTO users (email, name, role, avatar_color, invited_by, password_hash) VALUES (?, ?, ?, ?, ?, ?)').run(email, name, role, color, invitedBy, pwHash);
  return getUser(r.lastInsertRowid);
}

export function setPassword(userId, password) {
  const pwHash = hashPassword(password);
  db.prepare("UPDATE users SET password_hash = ?, password_changed_at = datetime('now'), failed_login_count = 0, locked_until = NULL WHERE id = ?").run(pwHash, userId);
  destroyUserSessions(userId);
}

export function validatePasswordStrength(password) {
  const value = String(password || '');
  const problems = [];
  if (value.length < 10) problems.push('mindestens 10 Zeichen');
  if (!/[a-z]/.test(value)) problems.push('einen Kleinbuchstaben');
  if (!/[A-Z]/.test(value)) problems.push('einen Großbuchstaben');
  if (!/\d/.test(value)) problems.push('eine Zahl');
  if (!/[^A-Za-z0-9]/.test(value)) problems.push('ein Sonderzeichen');
  return { valid: problems.length === 0, problems };
}

export function registerFailedLogin(userId, maxAttempts = 8, lockMinutes = 15) {
  if (!userId) return null;
  const user = getUser(userId);
  if (!user) return null;
  const count = Number(user.failed_login_count || 0) + 1;
  if (count >= maxAttempts) {
    const lockedUntil = new Date(Date.now() + lockMinutes * 60 * 1000).toISOString();
    db.prepare('UPDATE users SET failed_login_count = 0, locked_until = ? WHERE id = ?').run(lockedUntil, userId);
    destroyUserSessions(userId);
    return { locked: true, lockedUntil };
  }
  db.prepare('UPDATE users SET failed_login_count = ? WHERE id = ?').run(count, userId);
  return { locked: false, remaining: Math.max(0, maxAttempts - count) };
}

export function clearFailedLogins(userId) {
  if (!userId) return;
  db.prepare('UPDATE users SET failed_login_count = 0, locked_until = NULL WHERE id = ?').run(userId);
}

export function isUserLocked(user) {
  if (!user?.locked_until) return false;
  const until = Date.parse(user.locked_until);
  if (!Number.isFinite(until) || until <= Date.now()) {
    clearFailedLogins(user.id);
    return false;
  }
  return true;
}

export function getUser(id) {
  return db.prepare('SELECT * FROM users WHERE id = ?').get(id);
}

export function getUserByEmail(email) {
  return db.prepare('SELECT * FROM users WHERE email = ? COLLATE NOCASE').get(email);
}

export function getUsers() {
  return db.prepare("SELECT id, email, name, role, active, avatar_color, last_active_at, created_at, locked_until, failed_login_count, CASE WHEN password_hash IS NOT NULL AND password_hash != '' THEN 1 ELSE 0 END as has_password FROM users ORDER BY created_at").all();
}

export function updateUser(id, data) {
  const allowed = ['name', 'role', 'active', 'avatar_color'];
  const fields = [], values = [];
  for (const [k, rawValue] of Object.entries(data)) {
    if (!allowed.includes(k) || rawValue === undefined) continue;
    const v = k === 'role' ? (['admin', 'manager', 'editor', 'publisher', 'viewer'].includes(rawValue) ? rawValue : 'viewer') : rawValue;
    fields.push(`${k} = ?`); values.push(v);
  }
  if (!fields.length) return getUsers().find(user => Number(user.id) === Number(id)) || null;
  values.push(id);
  db.prepare(`UPDATE users SET ${fields.join(', ')} WHERE id = ?`).run(...values);
  return getUsers().find(user => Number(user.id) === Number(id)) || null;
}

export function deleteUser(id) {
  db.prepare('DELETE FROM sessions WHERE user_id = ?').run(id);
  db.prepare('DELETE FROM users WHERE id = ?').run(id);
}

export function touchUserActivity(userId) {
  db.prepare("UPDATE users SET last_active_at = datetime('now') WHERE id = ?").run(userId);
}

export function getUserCount() {
  return db.prepare('SELECT COUNT(*) as count FROM users').get().count;
}

// --- Sessions ---
export function createSession(userId, ip, userAgent) {
  const token = generateToken();
  const hash = hashToken(token);
  const now = new Date();
  const expiresAt = new Date(now.getTime() + SESSION_DURATION_MS).toISOString();
  const lastSeenAt = now.toISOString();
  db.prepare('INSERT INTO sessions (user_id, token_hash, ip, user_agent, expires_at, last_seen_at, revoked_at) VALUES (?, ?, ?, ?, ?, ?, NULL)').run(userId, hash, ip, userAgent || '', expiresAt, lastSeenAt);
  return { token, expiresAt };
}

export function validateSession(token) {
  if (!token) return null;
  const hash = hashToken(token);
  const session = db.prepare("SELECT s.*, u.email, u.name, u.role, u.active, u.avatar_color, u.locked_until FROM sessions s JOIN users u ON s.user_id = u.id WHERE s.token_hash = ? AND s.revoked_at IS NULL AND s.expires_at > datetime('now')").get(hash);
  if (!session || !session.active || isUserLocked(session)) return null;
  const lastSeen = Date.parse(session.last_seen_at || session.created_at || 0);
  if (Number.isFinite(lastSeen) && Date.now() - lastSeen > SESSION_IDLE_MS) {
    db.prepare("UPDATE sessions SET revoked_at = datetime('now') WHERE id = ?").run(session.id);
    return null;
  }
  if (!Number.isFinite(lastSeen) || Date.now() - lastSeen >= SESSION_TOUCH_INTERVAL_MS) {
    const now = new Date().toISOString();
    db.prepare('UPDATE sessions SET last_seen_at = ? WHERE id = ?').run(now, session.id);
    session.last_seen_at = now;
    touchUserActivity(session.user_id);
  }
  return session;
}

export function destroySession(token) {
  const hash = hashToken(token);
  db.prepare("UPDATE sessions SET revoked_at = datetime('now') WHERE token_hash = ?").run(hash);
}

export function destroySessionById(id) {
  db.prepare("UPDATE sessions SET revoked_at = datetime('now') WHERE id = ?").run(id);
}

export function getSessionById(id) {
  return db.prepare("SELECT s.id, s.user_id, s.ip, s.user_agent, s.created_at, s.last_seen_at, s.expires_at, s.revoked_at, u.email, u.name, u.role FROM sessions s JOIN users u ON s.user_id = u.id WHERE s.id = ?").get(id);
}

export function destroyOwnedSessionById(userId, id) {
  const result = db.prepare("UPDATE sessions SET revoked_at = datetime('now') WHERE id = ? AND user_id = ? AND revoked_at IS NULL").run(id, userId);
  return Number(result.changes || 0);
}

export function destroyUserSessions(userId) {
  db.prepare("UPDATE sessions SET revoked_at = datetime('now') WHERE user_id = ? AND revoked_at IS NULL").run(userId);
}

export function getUserSessions(userId) {
  return db.prepare("SELECT id, ip, user_agent, created_at, last_seen_at, expires_at FROM sessions WHERE user_id = ? AND revoked_at IS NULL AND expires_at > datetime('now') ORDER BY created_at DESC").all(userId);
}

export function getAllActiveSessions() {
  return db.prepare("SELECT s.id, s.user_id, s.ip, s.user_agent, s.created_at, s.last_seen_at, s.expires_at, u.email, u.name, u.role FROM sessions s JOIN users u ON s.user_id = u.id WHERE s.revoked_at IS NULL AND s.expires_at > datetime('now') ORDER BY s.created_at DESC").all();
}

// clean expired sessions periodically
export function cleanExpiredSessions() {
  db.prepare("DELETE FROM sessions WHERE expires_at <= datetime('now') OR (revoked_at IS NOT NULL AND revoked_at <= datetime('now', '-7 days'))").run();
}

// --- Auth Tokens (Magic Links) ---
export function createAuthToken(email, type = 'login', role = 'editor', createdBy = null) {
  const token = generateToken();
  const hash = hashToken(token);
  const minutes = type === 'invite' ? INVITE_TOKEN_EXPIRY_HOURS * 60 : LOGIN_TOKEN_EXPIRY_MIN;
  const expiresAt = new Date(Date.now() + minutes * 60 * 1000).toISOString();

  // invalidate previous unused tokens of same type for same email
  db.prepare("UPDATE auth_tokens SET used = 1 WHERE email = ? COLLATE NOCASE AND type = ? AND used = 0").run(email, type);

  db.prepare('INSERT INTO auth_tokens (email, token_hash, type, role, expires_at, created_by) VALUES (?, ?, ?, ?, ?, ?)').run(email, hash, type, role, expiresAt, createdBy);
  return { token, expiresAt };
}

export function validateAuthToken(token) {
  const hash = hashToken(token);
  const row = db.prepare("SELECT * FROM auth_tokens WHERE token_hash = ? AND used = 0 AND expires_at > datetime('now')").get(hash);
  if (!row) return null;
  db.prepare('UPDATE auth_tokens SET used = 1 WHERE id = ?').run(row.id);
  return row;
}

// --- Rate Limiting ---
const rateLimits = new Map();

export function checkRateLimit(key, maxAttempts = 5, windowMs = 15 * 60 * 1000) {
  const now = Date.now();
  const entry = rateLimits.get(key);
  if (!entry) {
    rateLimits.set(key, { count: 1, firstAttempt: now });
    return { allowed: true, remaining: maxAttempts - 1 };
  }
  if (now - entry.firstAttempt > windowMs) {
    rateLimits.set(key, { count: 1, firstAttempt: now });
    return { allowed: true, remaining: maxAttempts - 1 };
  }
  entry.count++;
  if (entry.count > maxAttempts) {
    const retryAfter = Math.ceil((entry.firstAttempt + windowMs - now) / 1000);
    return { allowed: false, remaining: 0, retryAfter };
  }
  return { allowed: true, remaining: maxAttempts - entry.count };
}

// --- CSRF ---
export function generateCsrfToken() {
  return randomBytes(24).toString('hex');
}

// --- Audit Log ---
export function auditLog(userId, userEmail, action, targetType = null, targetId = null, details = null, ip = null) {
  db.prepare('INSERT INTO audit_log (user_id, user_email, action, target_type, target_id, details, ip) VALUES (?, ?, ?, ?, ?, ?, ?)').run(userId, userEmail, action, targetType, targetId, details, ip);
}

export function getAuditLog(limit = 100, offset = 0) {
  return db.prepare('SELECT * FROM audit_log ORDER BY created_at DESC LIMIT ? OFFSET ?').all(limit, offset);
}

export function searchAuditLog({ limit = 100, offset = 0, userId = null, action = '', from = '', to = '', query = '' } = {}) {
  const clauses = [];
  const values = [];
  if (userId) { clauses.push('user_id = ?'); values.push(Number(userId)); }
  if (action) { clauses.push('action LIKE ?'); values.push(`%${String(action).slice(0, 80)}%`); }
  if (from) { clauses.push('created_at >= ?'); values.push(String(from).slice(0, 30)); }
  if (to) { clauses.push('created_at <= ?'); values.push(String(to).slice(0, 30)); }
  if (query) {
    const q = `%${String(query).slice(0, 120)}%`;
    clauses.push('(user_email LIKE ? OR action LIKE ? OR target_type LIKE ? OR target_id LIKE ? OR details LIKE ? OR ip LIKE ?)');
    values.push(q, q, q, q, q, q);
  }
  const where = clauses.length ? `WHERE ${clauses.join(' AND ')}` : '';
  const safeLimit = Math.max(1, Math.min(1000, Number(limit) || 100));
  const safeOffset = Math.max(0, Number(offset) || 0);
  const rows = db.prepare(`SELECT * FROM audit_log ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...values, safeLimit, safeOffset);
  const total = db.prepare(`SELECT COUNT(*) AS count FROM audit_log ${where}`).get(...values)?.count || 0;
  return { rows, total, limit: safeLimit, offset: safeOffset };
}

export function getAuditLogForUser(userId, limit = 50) {
  return db.prepare('SELECT * FROM audit_log WHERE user_id = ? ORDER BY created_at DESC LIMIT ?').all(userId, limit);
}

// --- Login History ---
export function recordLogin(userId, ip, userAgent, success = true) {
  db.prepare('INSERT INTO login_history (user_id, ip, user_agent, success) VALUES (?, ?, ?, ?)').run(userId, ip, userAgent || '', success ? 1 : 0);
}

export function getLoginHistory(userId, limit = 20) {
  return db.prepare('SELECT * FROM login_history WHERE user_id = ? ORDER BY created_at DESC LIMIT ?').all(userId, limit);
}

export function getRecentFailedLogins(email, windowMinutes = 15) {
  const user = getUserByEmail(email);
  if (!user) return 0;
  const r = db.prepare("SELECT COUNT(*) as count FROM login_history WHERE user_id = ? AND success = 0 AND created_at > datetime('now', ?)").get(user.id, `-${windowMinutes} minutes`);
  return r.count;
}

// --- Setup: ensure at least one admin exists ---
export function ensureAdminExists() {
  const count = getUserCount();
  return count === 0;
}

export function isSetupMode() {
  return getUserCount() === 0;
}

// Clean expired sessions every 10 minutes
setInterval(cleanExpiredSessions, 10 * 60 * 1000);
