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

// Per-company chart of accounts plus the GL "posting defaults" — the fallback accounts the
// depreciation and PM ledgers post to when an asset doesn't carry its own. Every change (account
// create/update/delete and a defaults change) is logged via audit() for the Activity & Audit Trail.

const TYPES = ['asset', 'contra_asset', 'liability', 'equity', 'revenue', 'expense'];
const BALANCES = ['debit', 'credit'];
// Used as a last-resort fallback if a company somehow has no defaults row (seed normally creates one).
const FALLBACK_DEFAULTS = {
  asset_account: '1500', accumulated_account: '1590', expense_account: '6400',
  pm_expense_account: '6450', pm_clearing_account: '2000',
  cwip_account: '1800', gain_account: '7200', loss_account: '7300',
  proceeds_account: '1010', retained_earnings_account: '3900'
};
const DEFAULT_KEYS = Object.keys(FALLBACK_DEFAULTS);

function fromRow(row) {
  if (!row) return null;
  return { ...row, active: Boolean(row.active) };
}

function listAccounts(companyId) {
  return all('SELECT * FROM gl_accounts WHERE entity_id = ? ORDER BY code', [companyId]).map(fromRow);
}

function getAccount(id, companyId) {
  return fromRow(one('SELECT * FROM gl_accounts WHERE id = ? AND entity_id = ?', [id, companyId]));
}

function normalize(body) {
  return {
    code: String(body.code || '').trim(),
    name: String(body.name || '').trim(),
    type: TYPES.includes(body.type) ? body.type : 'asset',
    normal_balance: BALANCES.includes(body.normal_balance) ? body.normal_balance : 'debit',
    description: body.description ? String(body.description).trim() : null,
    active: body.active === false ? 0 : 1
  };
}

function createAccount(body, userId, companyId) {
  const input = normalize(body);
  if (!input.code) throw Object.assign(new Error('An account code is required'), { status: 400 });
  if (!input.name) throw Object.assign(new Error('An account name is required'), { status: 400 });
  if (one('SELECT id FROM gl_accounts WHERE entity_id = ? AND code = ?', [companyId, input.code])) {
    throw Object.assign(new Error(`Account ${input.code} already exists`), { status: 400 });
  }
  const ts = now();
  const result = run(
    `INSERT INTO gl_accounts (entity_id, code, name, type, normal_balance, description, active, created_by, created_at, updated_at)
     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
    [companyId, input.code, input.name, input.type, input.normal_balance, input.description, input.active, userId, ts, ts]
  );
  const account = getAccount(result.lastInsertRowid, companyId);
  audit(userId, 'create', 'gl_account', result.lastInsertRowid, null, account);
  return account;
}

function updateAccount(id, body, userId, companyId) {
  const before = getAccount(id, companyId);
  if (!before) return null;
  const input = normalize({ ...before, ...body });
  if (input.code !== before.code && one('SELECT id FROM gl_accounts WHERE entity_id = ? AND code = ? AND id <> ?', [companyId, input.code, id])) {
    throw Object.assign(new Error(`Account ${input.code} already exists`), { status: 400 });
  }
  run(
    'UPDATE gl_accounts SET code = ?, name = ?, type = ?, normal_balance = ?, description = ?, active = ?, updated_at = ? WHERE id = ?',
    [input.code, input.name, input.type, input.normal_balance, input.description, input.active, now(), id]
  );
  const after = getAccount(id, companyId);
  audit(userId, 'update', 'gl_account', id, before, after);
  return after;
}

function deleteAccount(id, userId, companyId) {
  const before = getAccount(id, companyId);
  if (!before) return false;
  run('DELETE FROM gl_accounts WHERE id = ?', [id]);
  audit(userId, 'delete', 'gl_account', id, before, null);
  return true;
}

// ---- Posting defaults -------------------------------------------------------

function getDefaults(companyId) {
  const row = one('SELECT * FROM gl_account_defaults WHERE entity_id = ?', [companyId]);
  if (!row) return { entity_id: companyId, ...FALLBACK_DEFAULTS };
  return row;
}

function saveDefaults(body, userId, companyId) {
  const before = getDefaults(companyId);
  const next = {};
  for (const key of DEFAULT_KEYS) next[key] = body[key] !== undefined ? (String(body[key] || '').trim() || null) : (before[key] || null);
  run(
    `INSERT INTO gl_account_defaults
       (entity_id, asset_account, accumulated_account, expense_account, pm_expense_account, pm_clearing_account,
        cwip_account, gain_account, loss_account, proceeds_account, retained_earnings_account, updated_at)
     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
     ON CONFLICT(entity_id) DO UPDATE SET
       asset_account = excluded.asset_account, accumulated_account = excluded.accumulated_account,
       expense_account = excluded.expense_account, pm_expense_account = excluded.pm_expense_account,
       pm_clearing_account = excluded.pm_clearing_account, cwip_account = excluded.cwip_account,
       gain_account = excluded.gain_account, loss_account = excluded.loss_account,
       proceeds_account = excluded.proceeds_account, retained_earnings_account = excluded.retained_earnings_account,
       updated_at = excluded.updated_at`,
    [companyId, next.asset_account, next.accumulated_account, next.expense_account, next.pm_expense_account, next.pm_clearing_account,
      next.cwip_account, next.gain_account, next.loss_account, next.proceeds_account, next.retained_earnings_account, now()]
  );
  const after = getDefaults(companyId);
  audit(userId, 'update', 'gl_account_defaults', companyId, before, after);
  return after;
}

module.exports = { TYPES, createAccount, deleteAccount, getAccount, getDefaults, listAccounts, saveDefaults, updateAccount };
