const { all, audit, now, one, run, seedBooksForCompany, seedGlAccountsForCompany, seedReferenceValuesForCompany } = require('../db');

// Companies are stored in the `entities` table. Each gets its own books/GL, PM plans, alerts, and
// (in phase 2) reference data. Disposing a company is a soft status change that preserves all history.

function listCompanies({ includeDisposed = false } = {}) {
  const where = includeDisposed ? '' : "WHERE status = 'active'";
  return all(`SELECT * FROM entities ${where} ORDER BY status, name`);
}

function getCompany(id) {
  return one('SELECT * FROM entities WHERE id = ?', [id]);
}

function activeCount() {
  return one("SELECT COUNT(*) AS c FROM entities WHERE status = 'active'").c;
}

function createCompany(body, userId) {
  const name = String(body.name || '').trim();
  if (!name) throw Object.assign(new Error('A company name is required'), { status: 400 });
  const ts = now();
  const result = run(
    `INSERT INTO entities (name, legal_name, federal_tax_id, state_tax_id, local_tax_id, fiscal_year_end_month, base_currency, status, created_at, updated_at)
     VALUES (?, ?, ?, ?, ?, ?, ?, 'active', ?, ?)`,
    [
      name, body.legal_name || null,
      body.federal_tax_id || null, body.state_tax_id || null, body.local_tax_id || null,
      Number(body.fiscal_year_end_month || 12), body.base_currency || 'USD', ts, ts
    ]
  );
  // A new company starts with the standard book set, reference data, and chart of accounts + defaults.
  seedBooksForCompany(result.lastInsertRowid);
  seedReferenceValuesForCompany(result.lastInsertRowid);
  seedGlAccountsForCompany(result.lastInsertRowid);
  audit(userId, 'create', 'entity', result.lastInsertRowid, null, { name });
  return getCompany(result.lastInsertRowid);
}

function updateCompany(id, body, userId) {
  const before = getCompany(id);
  if (!before) return null;
  run(
    `UPDATE entities SET name = ?, legal_name = ?, federal_tax_id = ?, state_tax_id = ?, local_tax_id = ?,
       fiscal_year_end_month = ?, base_currency = ?, updated_at = ? WHERE id = ?`,
    [
      body.name !== undefined ? String(body.name).trim() || before.name : before.name,
      body.legal_name !== undefined ? body.legal_name : before.legal_name,
      body.federal_tax_id !== undefined ? body.federal_tax_id : before.federal_tax_id,
      body.state_tax_id !== undefined ? body.state_tax_id : before.state_tax_id,
      body.local_tax_id !== undefined ? body.local_tax_id : before.local_tax_id,
      body.fiscal_year_end_month !== undefined ? Number(body.fiscal_year_end_month) : before.fiscal_year_end_month,
      body.base_currency !== undefined ? body.base_currency : before.base_currency,
      now(), id
    ]
  );
  audit(userId, 'update', 'entity', id, before, body);
  return getCompany(id);
}

// Archive/retire a company: it stays in the database (assets, books, GL preserved) but is hidden from
// active selection and can no longer be the active company. Guards the last active company.
function disposeCompany(id, body, userId) {
  const before = getCompany(id);
  if (!before) return { ok: false, status: 404, error: 'Company not found' };
  if (before.status === 'disposed') return { ok: false, status: 400, error: 'Company is already disposed' };
  if (activeCount() <= 1) return { ok: false, status: 400, error: 'At least one active company is required' };
  run("UPDATE entities SET status = 'disposed', disposed_at = ?, disposed_reason = ?, updated_at = ? WHERE id = ?",
    [now(), body.reason || null, now(), id]);
  audit(userId, 'dispose', 'entity', id, before, { reason: body.reason || null });
  return { ok: true, company: getCompany(id) };
}

function restoreCompany(id, userId) {
  const before = getCompany(id);
  if (!before) return null;
  run("UPDATE entities SET status = 'active', disposed_at = NULL, disposed_reason = NULL, updated_at = ? WHERE id = ?", [now(), id]);
  audit(userId, 'restore', 'entity', id, before, null);
  return getCompany(id);
}

// ---- Per-company user access ------------------------------------------------

// A user who can manage users (admin.users / wildcard) sees every company; this prevents an admin
// from locking themselves out of any company. Required lazily to avoid a load-time cycle.
function isCompanyAdmin(user) {
  const { capabilitiesForRole } = require('./roles');
  const { capabilitySetIncludes } = require('./accessControl');
  return capabilitySetIncludes(capabilitiesForRole(user.role), 'admin.users');
}

// Active companies a user may see/switch to. Admins → all; others → their assigned companies, falling
// back to the default company when they have none (so a user is never locked out).
function companiesForUser(user) {
  if (isCompanyAdmin(user)) {
    return all("SELECT * FROM entities WHERE status = 'active' ORDER BY name");
  }
  const rows = all(
    `SELECT e.* FROM entities e JOIN user_companies uc ON uc.entity_id = e.id
     WHERE uc.user_id = ? AND e.status = 'active' ORDER BY e.name`,
    [user.id]
  );
  if (rows.length) return rows;
  const def = one("SELECT * FROM entities WHERE status = 'active' ORDER BY id LIMIT 1");
  return def ? [def] : [];
}

function companyIdsForUser(user) {
  return companiesForUser(user).map((c) => c.id);
}

// Explicit assignments stored for a user (independent of the admin bypass).
function assignedCompanyIds(userId) {
  return all('SELECT entity_id FROM user_companies WHERE user_id = ?', [userId]).map((r) => r.entity_id);
}

function setUserCompanies(userId, ids, actorId) {
  const valid = (Array.isArray(ids) ? ids : []).filter((id) => one('SELECT id FROM entities WHERE id = ?', [id]));
  run('DELETE FROM user_companies WHERE user_id = ?', [userId]);
  for (const id of valid) run('INSERT OR IGNORE INTO user_companies (user_id, entity_id) VALUES (?, ?)', [userId, id]);
  if (actorId) audit(actorId, 'set_companies', 'user', userId, null, { company_ids: valid });
}

module.exports = {
  assignedCompanyIds,
  companiesForUser,
  companyIdsForUser,
  createCompany,
  disposeCompany,
  getCompany,
  isCompanyAdmin,
  listCompanies,
  restoreCompany,
  setUserCompanies,
  updateCompany
};
