const { all, audit, now, one, run, tx } = require('../db');
const { annualSchedule, money, section280fRecapture } = require('../depreciation');
const { ruleSetForBook } = require('./ruleSets');
const { hydrateAsset } = require('./assets');

const DISPOSAL_METHODS = ['sale', 'exchange', 'involuntary_conversion', 'like_kind', 'abandonment'];

// Reject a subledger write whose effective date falls in a closed period (primary book). No-op until a
// period is actually locked. Lazy-required to avoid the closePeriods↔services cycle.
function guardSubledger(assetId, date) {
  const row = one('SELECT entity_id FROM assets WHERE id = ?', [assetId]);
  if (row && date) require('./closePeriods').assertSubledgerOpen(row.entity_id, date);
}

function yearOf(value) {
  if (!value) return new Date().getFullYear();
  return new Date(`${value}T00:00:00`).getFullYear();
}

function netAdjustments(assetId, bookType) {
  // Impairments and write-downs reduce carrying value; write-ups/revaluations restore it.
  const rows = all('SELECT type, amount FROM asset_adjustments WHERE asset_id = ? AND book_type = ?', [assetId, bookType]);
  return rows.reduce((sum, row) => {
    const sign = row.type === 'write_up' || row.type === 'revaluation' ? -1 : 1;
    return sum + sign * Number(row.amount || 0);
  }, 0);
}

function bookFor(asset, bookType) {
  return asset.books.find((book) => book.book_type === bookType) || asset.books[0] || { book_type: bookType };
}

// Real property (§1250) vs personal property (§1245), by recovery period: 27.5-year residential rental
// and 39-year nonresidential real property are §1250; everything else is §1245.
function isRealProperty(asset, book) {
  const months = Number(book.useful_life_months || asset.useful_life_months || 0);
  const pt = String(asset.property_type || '').toLowerCase();
  return months >= 330 || /real|realty|1250|building/.test(pt);
}

// Characterize the gain on disposal into ordinary-income recapture vs. §1231 (capital) gain (Form 4797).
// §1245: ordinary recapture = lesser of gain or total depreciation taken (incl. §179/bonus). §1250:
// ordinary recapture = lesser of gain or "additional depreciation" (accelerated over straight-line); the
// remaining gain up to straight-line taken is unrecaptured §1250 gain (a 25% capital bucket).
function characterizeRecapture(asset, book, rules, year, proportion, gainLoss, accumulated) {
  const section = isRealProperty(asset, book) ? '1250' : '1245';
  const gain = Math.max(0, gainLoss);
  let ordinary = 0;
  let unrecaptured1250 = 0;
  if (gain > 0) {
    if (section === '1245') {
      ordinary = Math.min(gain, accumulated);
    } else {
      // Straight-line accumulated for the same period (additional depreciation = accelerated − SL).
      const slBook = { ...book, depreciation_method: 'straight_line', section_179_amount: 0, bonus_percent: 0 };
      const slRow = annualSchedule(asset, slBook, rules, { startYear: year, endYear: year })[0] || {};
      const slAccum = money(Number(slRow.accumulated || 0) * proportion);
      const additional = Math.max(0, money(accumulated - slAccum));
      ordinary = Math.min(gain, additional);
      unrecaptured1250 = Math.min(money(gain - ordinary), slAccum);
    }
  }
  return {
    recapture_section: section,
    ordinary_recapture: money(ordinary),
    unrecaptured_1250_gain: money(unrecaptured1250),
    section_1231_gain: money(gainLoss - ordinary) // negative for a §1231 loss
  };
}

function computeDisposal(asset, input = {}) {
  const bookType = input.book_type || 'GAAP';
  const book = bookFor(asset, bookType);
  const rules = ruleSetForBook(bookType);
  const disposalDate = input.disposal_date || new Date().toISOString().slice(0, 10);
  const year = yearOf(disposalDate);

  const totalCost = Number(book.cost ?? asset.acquisition_cost ?? 0);
  const partial = Boolean(input.partial);
  const disposedCost = partial ? Math.min(totalCost, Number(input.disposed_cost || 0)) : totalCost;
  const proportion = totalCost > 0 ? disposedCost / totalCost : 1;

  const row = annualSchedule(asset, book, rules, { startYear: year, endYear: year })[0] || {};
  const accumulatedFull = Number(row.accumulated || 0);
  const nbvFull = row.netBookValue != null ? Number(row.netBookValue) : Math.max(0, totalCost - accumulatedFull);

  const accumulated = money(accumulatedFull * proportion);
  const impairment = money(netAdjustments(asset.id, bookType) * proportion);
  const netBookValue = money(Math.max(0, nbvFull * proportion - impairment));

  const price = Number(input.disposal_price || 0);
  const expense = Number(input.disposal_expense || 0);
  const realized = money(price - expense);
  const gainLoss = money(realized - netBookValue);

  // Recapture characterization is computed on the disposed book's depreciation (use a tax book — FEDERAL —
  // for tax recapture). "Depreciation taken" includes the impairment write-down already reflected in NBV.
  const recapture = characterizeRecapture(asset, book, rules, year, proportion, gainLoss, money(accumulated + impairment));

  return {
    book_type: bookType,
    disposal_date: disposalDate,
    method: DISPOSAL_METHODS.includes(input.method) ? input.method : 'sale',
    property_type: input.property_type || asset.property_type || null,
    partial: partial ? 1 : 0,
    disposed_cost: money(disposedCost),
    total_cost: money(totalCost),
    disposal_price: money(price),
    disposal_expense: money(expense),
    accumulated_depreciation: accumulated,
    impairment,
    net_book_value: netBookValue,
    realized,
    gain_loss: gainLoss,
    ...recapture
  };
}

function previewDisposal(assetId, input) {
  const asset = hydrateAsset(assetId);
  if (!asset) return null;
  return computeDisposal(asset, input);
}

// §280F recapture preview for a listed/passenger asset converting to ≤50% business use in a given year.
function previewSection280fRecapture(assetId, input = {}) {
  const asset = hydrateAsset(assetId);
  if (!asset) return null;
  const bookType = input.book || 'FEDERAL';
  const book = bookFor(asset, bookType);
  const year = Number(input.year) || new Date().getFullYear();
  return section280fRecapture(asset, book, ruleSetForBook(bookType), year);
}

function recordDisposal(assetId, input, userId) {
  require('./midQuarter').clearCache();
  const asset = hydrateAsset(assetId);
  if (!asset) return null;
  const result = computeDisposal(asset, input);
  guardSubledger(assetId, result.disposal_date);
  const timestamp = now();

  return tx(() => {
    const inserted = run(
      `INSERT INTO disposals (
        asset_id, disposal_date, method, property_type, partial, disposed_cost,
        disposal_price, disposal_expense, accumulated_depreciation, net_book_value,
        gain_loss, book_type, recapture_section, ordinary_recapture, section_1231_gain,
        unrecaptured_1250_gain, notes, created_by, created_at
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
      [
        assetId, result.disposal_date, result.method, result.property_type, result.partial,
        result.disposed_cost, result.disposal_price, result.disposal_expense,
        result.accumulated_depreciation, result.net_book_value, result.gain_loss,
        result.book_type, result.recapture_section, result.ordinary_recapture, result.section_1231_gain,
        result.unrecaptured_1250_gain, input.notes || null, userId, timestamp
      ]
    );

    if (result.partial) {
      const totalCost = result.total_cost;
      const remainingProportion = totalCost > 0 ? Math.max(0, (totalCost - result.disposed_cost) / totalCost) : 1;
      run('UPDATE assets SET acquisition_cost = acquisition_cost - ?, updated_at = ? WHERE id = ?', [
        result.disposed_cost,
        timestamp,
        assetId
      ]);
      run('UPDATE asset_books SET cost = cost * ? WHERE asset_id = ? AND cost IS NOT NULL', [remainingProportion, assetId]);
    } else {
      run(
        `UPDATE assets SET
          status = 'disposed', disposal_date = ?, disposal_price = ?, disposal_expense = ?,
          disposal_type = ?, property_type = ?, updated_at = ?
         WHERE id = ?`,
        [result.disposal_date, result.disposal_price, result.disposal_expense, result.method, result.property_type, timestamp, assetId]
      );
    }

    const disposal = one('SELECT * FROM disposals WHERE id = ?', [inserted.lastInsertRowid]);
    audit(userId, 'dispose', 'asset', assetId, null, disposal);
    return { disposal, asset: hydrateAsset(assetId) };
  });
}

function reverseDisposal(assetId, disposalId, userId) {
  require('./midQuarter').clearCache();
  const disposal = one('SELECT * FROM disposals WHERE id = ? AND asset_id = ?', [disposalId, assetId]);
  if (!disposal) return null;
  guardSubledger(assetId, disposal.disposal_date);
  const timestamp = now();

  return tx(() => {
    run('DELETE FROM disposals WHERE id = ?', [disposalId]);
    if (disposal.partial) {
      run('UPDATE assets SET acquisition_cost = acquisition_cost + ?, updated_at = ? WHERE id = ?', [
        disposal.disposed_cost,
        timestamp,
        assetId
      ]);
    } else if (!one('SELECT id FROM disposals WHERE asset_id = ? AND partial = 0', [assetId])) {
      run(
        `UPDATE assets SET
          status = 'in_service', disposal_date = NULL, disposal_price = NULL,
          disposal_expense = NULL, disposal_type = NULL, updated_at = ?
         WHERE id = ?`,
        [timestamp, assetId]
      );
    }
    audit(userId, 'reverse_disposal', 'asset', assetId, disposal, null);
    return hydrateAsset(assetId);
  });
}

function recordAdjustment(assetId, input, userId) {
  const asset = one('SELECT id FROM assets WHERE id = ?', [assetId]);
  if (!asset) return null;
  const timestamp = now();
  guardSubledger(assetId, input.adjustment_date || timestamp.slice(0, 10));
  const result = run(
    `INSERT INTO asset_adjustments (asset_id, adjustment_date, type, amount, book_type, reason, created_by, created_at)
     VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
    [
      assetId,
      input.adjustment_date || timestamp.slice(0, 10),
      input.type || 'impairment',
      Number(input.amount || 0),
      input.book_type || 'GAAP',
      input.reason || null,
      userId,
      timestamp
    ]
  );
  const adjustment = one('SELECT * FROM asset_adjustments WHERE id = ?', [result.lastInsertRowid]);
  audit(userId, 'adjust', 'asset', assetId, null, adjustment);
  return adjustment;
}

function deleteAdjustment(assetId, adjustmentId, userId) {
  const result = run('DELETE FROM asset_adjustments WHERE id = ? AND asset_id = ?', [adjustmentId, assetId]);
  if (!result.changes) return false;
  audit(userId, 'delete', 'asset_adjustment', adjustmentId, null, { asset_id: assetId });
  return true;
}

module.exports = {
  DISPOSAL_METHODS,
  computeDisposal,
  deleteAdjustment,
  netAdjustments,
  previewDisposal,
  previewSection280fRecapture,
  recordAdjustment,
  recordDisposal,
  reverseDisposal
};
