const { all, audit, json, now, one, parseJson, run } = require('../db');
const { CAPABILITY_KEYS, WILDCARD, capabilitySetIncludes, sanitizeCapabilities } = require('./accessControl');

function slugify(value) {
  return String(value || '')
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '_')
    .replace(/^_+|_+$/g, '')
    .slice(0, 40);
}

function fromRow(row) {
  if (!row) return null;
  return {
    key: row.key,
    name: row.name,
    description: row.description || '',
    capabilities: parseJson(row.capabilities_json, []),
    is_system: Boolean(row.is_system),
    locked: Boolean(row.locked),
    user_count: one('SELECT COUNT(*) AS c FROM users WHERE role = ?', [row.key]).c
  };
}

function listRoles() {
  return all('SELECT * FROM roles ORDER BY sort_order, name').map(fromRow);
}

function getRole(key) {
  return fromRow(one('SELECT * FROM roles WHERE key = ?', [key]));
}

function roleExists(key) {
  return Boolean(one('SELECT key FROM roles WHERE key = ?', [key]));
}

// Capabilities granted to a role key (empty when the role is unknown).
function capabilitiesForRole(key) {
  const row = one('SELECT capabilities_json FROM roles WHERE key = ?', [key]);
  return row ? parseJson(row.capabilities_json, []) : [];
}

function roleHasCapability(key, capability) {
  return capabilitySetIncludes(capabilitiesForRole(key), capability);
}

function createRole(body, userId) {
  const name = String(body.name || '').trim();
  if (!name) throw Object.assign(new Error('A role name is required'), { status: 400 });
  const key = slugify(body.key || name);
  if (!key) throw Object.assign(new Error('A valid role key is required'), { status: 400 });
  if (roleExists(key)) throw Object.assign(new Error('A role with that key already exists'), { status: 400 });
  const capabilities = sanitizeCapabilities(body.capabilities);
  const ts = now();
  const sort = (one('SELECT MAX(sort_order) AS m FROM roles').m || 0) + 1;
  run(
    'INSERT INTO roles (key, name, description, capabilities_json, is_system, locked, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, 0, 0, ?, ?, ?)',
    [key, name, body.description || null, json(capabilities), sort, ts, ts]
  );
  audit(userId, 'create', 'role', key, null, { name, capabilities });
  return getRole(key);
}

function updateRole(key, body, userId) {
  const before = one('SELECT * FROM roles WHERE key = ?', [key]);
  if (!before) return null;
  const name = body.name !== undefined ? String(body.name).trim() || before.name : before.name;
  // Locked roles (admin) always keep the wildcard capability set.
  const capabilities = before.locked
    ? parseJson(before.capabilities_json, [WILDCARD])
    : (body.capabilities !== undefined ? sanitizeCapabilities(body.capabilities) : parseJson(before.capabilities_json, []));
  run(
    'UPDATE roles SET name = ?, description = ?, capabilities_json = ?, updated_at = ? WHERE key = ?',
    [name, body.description ?? before.description, json(capabilities), now(), key]
  );
  audit(userId, 'update', 'role', key, fromRow(before), { name, capabilities });
  return getRole(key);
}

function deleteRole(key, userId) {
  const before = one('SELECT * FROM roles WHERE key = ?', [key]);
  if (!before) return { ok: false, status: 404, error: 'Role not found' };
  if (before.is_system) return { ok: false, status: 400, error: 'Built-in roles cannot be deleted' };
  const inUse = one('SELECT COUNT(*) AS c FROM users WHERE role = ?', [key]).c;
  if (inUse) return { ok: false, status: 400, error: `Reassign the ${inUse} user(s) with this role before deleting it` };
  run('DELETE FROM roles WHERE key = ?', [key]);
  audit(userId, 'delete', 'role', key, fromRow(before), null);
  return { ok: true };
}

module.exports = {
  CAPABILITY_KEYS,
  capabilitiesForRole,
  createRole,
  deleteRole,
  getRole,
  listRoles,
  roleExists,
  roleHasCapability,
  updateRole
};
