const express = require('express');
const router = express.Router();
const { getDb, ensureUserDefaultCategories } = require('../db/database');
const { VALID_VISIBILITY, getValidCycleTypes, parseDueDay, parseInterestRate, validateCycleDay, validateBillData, computeBalanceDelta } = require('../services/billsService');
const { amortizationSchedule, debtAprSnapshot } = require('../services/aprService');
const { standardizeError } = require('../middleware/errorFormatter');
const { validatePaymentInput } = require('../services/paymentValidation');

function hasText(value) {
  return typeof value === 'string' && value.trim().length > 0;
}

function isDebtBill(bill) {
  const category = String(bill.category_name || '').toLowerCase();
  return Number(bill.current_balance) > 0
    || bill.minimum_payment != null
    || ['credit card', 'credit cards', 'loan', 'loans', 'debt'].some(token => category.includes(token));
}

function issue(bill, field, severity, suggestion) {
  return {
    bill_id:       bill.id,
    bill_name:     bill.name,
    field,
    severity,
    suggestion,
  };
}

// ── GET /api/bills ────────────────────────────────────────────────────────────
router.get('/', (req, res) => {
  const db = getDb();
  ensureUserDefaultCategories(req.user.id);
  const includeInactive = req.query.inactive === 'true';
  const bills = db.prepare(`
    SELECT b.*, c.name AS category_name,
      CASE WHEN EXISTS(
        SELECT 1 FROM bill_history_ranges WHERE bill_id = b.id
      ) THEN 1 ELSE 0 END AS has_history_ranges
    FROM bills b
    LEFT JOIN categories c ON b.category_id = c.id AND c.deleted_at IS NULL
    WHERE b.user_id = ?
      AND b.deleted_at IS NULL
      ${includeInactive ? '' : 'AND b.active = 1'}
    ORDER BY b.due_day ASC, b.name ASC
  `).all(req.user.id);
  res.json(bills);
});

// ── GET /api/bills/audit?inactive=true ───────────────────────────────────────
router.get('/audit', (req, res) => {
  const db = getDb();
  ensureUserDefaultCategories(req.user.id);
  const includeInactive = req.query.inactive === 'true';
  const bills = db.prepare(`
    SELECT b.id, b.name, b.category_id, b.due_day, b.active, b.autopay_enabled,
           b.website, b.username, b.account_info, b.current_balance,
           b.minimum_payment, b.interest_rate, c.name AS category_name
    FROM bills b
    LEFT JOIN categories c ON b.category_id = c.id AND c.deleted_at IS NULL
    WHERE b.user_id = ?
      AND b.deleted_at IS NULL
      ${includeInactive ? '' : 'AND b.active = 1'}
    ORDER BY b.active DESC, b.due_day ASC, b.name ASC
  `).all(req.user.id);

  const auditedBills = bills.map((bill) => {
    const issues = [];
    const dueDay = Number(bill.due_day);
    const debt = isDebtBill(bill);
    const balance = Number(bill.current_balance);

    if (!Number.isInteger(dueDay) || dueDay < 1 || dueDay > 31) {
      issues.push(issue(bill, 'due_day', 'error', 'Add a due day between 1 and 31 so tracker periods and reminders can place this bill correctly.'));
    }
    if (!bill.category_id || !bill.category_name) {
      issues.push(issue(bill, 'category_id', 'warning', 'Choose a category so summaries, filters, and snowball debt detection stay accurate.'));
    }
    if (debt && !(Number(bill.minimum_payment) > 0)) {
      issues.push(issue(bill, 'minimum_payment', 'error', 'Add the required minimum payment so debt snowball projections can calculate payoff order and dates.'));
    }
    if (bill.autopay_enabled && !hasText(bill.website) && !hasText(bill.account_info)) {
      issues.push(issue(bill, 'autopay_enabled', 'warning', 'Add a website or account note so autopay bills still have enough reference information when something needs attention.'));
    }
    if (Number.isFinite(balance) && balance > 0 && bill.interest_rate == null) {
      issues.push(issue(bill, 'interest_rate', 'warning', 'Add the APR so snowball and amortization estimates include interest instead of assuming 0%.'));
    }

    return {
      id:            bill.id,
      name:          bill.name,
      active:        !!bill.active,
      category_name: bill.category_name,
      due_day:       bill.due_day,
      is_debt:       debt,
      issues,
    };
  });

  const issues = auditedBills.flatMap(bill => bill.issues);
  res.json({
    bills: auditedBills.filter(bill => bill.issues.length > 0),
    summary: {
      audited_bills: bills.length,
      issue_count:   issues.length,
      error_count:   issues.filter(item => item.severity === 'error').length,
      warning_count: issues.filter(item => item.severity === 'warning').length,
    },
  });
});

// ── GET /api/bills/:id/monthly-state?year=&month= ─────────────────────────────
router.get('/:id/monthly-state', (req, res) => {
  const db = getDb();
  const billId = parseInt(req.params.id, 10);
  if (!db.prepare('SELECT id FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(billId, req.user.id))
    return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));

  const year  = parseInt(req.query.year,  10);
  const month = parseInt(req.query.month, 10);
  if (isNaN(year)  || year  < 2000 || year  > 2100)
    return res.status(400).json(standardizeError('year must be a 4-digit integer between 2000 and 2100', 'VALIDATION_ERROR', 'year'));
  if (isNaN(month) || month < 1    || month > 12)
    return res.status(400).json(standardizeError('month must be an integer between 1 and 12', 'VALIDATION_ERROR', 'month'));

  const mbs = db.prepare(
    'SELECT actual_amount, notes, is_skipped FROM monthly_bill_state WHERE bill_id=? AND year=? AND month=?'
  ).get(billId, year, month);

  res.json({
    bill_id:       billId,
    year,
    month,
    actual_amount: mbs?.actual_amount ?? null,
    notes:         mbs?.notes         ?? null,
    is_skipped:    !!(mbs?.is_skipped),
  });
});

// ── PUT /api/bills/:id/monthly-state ──────────────────────────────────────────
router.put('/:id/monthly-state', (req, res) => {
  const db = getDb();
  const billId = parseInt(req.params.id, 10);
  if (!db.prepare('SELECT id FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(billId, req.user.id))
    return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));

  const { year, month, actual_amount, notes, is_skipped } = req.body;

  const y = parseInt(year,  10);
  const m = parseInt(month, 10);
  if (isNaN(y) || y < 2000 || y > 2100)
    return res.status(400).json(standardizeError('year must be a 4-digit integer between 2000 and 2100', 'VALIDATION_ERROR', 'year'));
  if (isNaN(m) || m < 1 || m > 12)
    return res.status(400).json(standardizeError('month must be an integer between 1 and 12', 'VALIDATION_ERROR', 'month'));

  if (actual_amount !== undefined && actual_amount !== null) {
    const amt = parseFloat(actual_amount);
    if (isNaN(amt) || amt < 0)
      return res.status(400).json(standardizeError('actual_amount must be a non-negative number or null', 'VALIDATION_ERROR', 'actual_amount'));
  }

  const amt      = actual_amount !== undefined ? (actual_amount === null ? null : parseFloat(actual_amount)) : null;
  const noteVal  = notes      !== undefined ? (notes      || null) : null;
  const skipVal  = is_skipped !== undefined ? (is_skipped ? 1 : 0) : 0;

  db.prepare(`
    INSERT INTO monthly_bill_state (bill_id, year, month, actual_amount, notes, is_skipped, updated_at)
    VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
    ON CONFLICT(bill_id, year, month) DO UPDATE SET
      actual_amount = excluded.actual_amount,
      notes         = excluded.notes,
      is_skipped    = excluded.is_skipped,
      updated_at    = datetime('now')
  `).run(billId, y, m, amt, noteVal, skipVal);

  const saved = db.prepare(
    'SELECT * FROM monthly_bill_state WHERE bill_id=? AND year=? AND month=?'
  ).get(billId, y, m);

  res.json({
    bill_id:       saved.bill_id,
    year:          saved.year,
    month:         saved.month,
    actual_amount: saved.actual_amount,
    notes:         saved.notes,
    is_skipped:    !!saved.is_skipped,
    created_at:    saved.created_at,
    updated_at:    saved.updated_at,
  });
});

// ── GET /api/bills/:id ────────────────────────────────────────────────────────
router.get('/:id', (req, res) => {
  const db = getDb();
  const bill = db.prepare(`
    SELECT b.*, c.name AS category_name,
      CASE WHEN EXISTS(
        SELECT 1 FROM bill_history_ranges WHERE bill_id = b.id
      ) THEN 1 ELSE 0 END AS has_history_ranges
    FROM bills b
    LEFT JOIN categories c ON b.category_id = c.id AND c.deleted_at IS NULL
    WHERE b.id = ? AND b.user_id = ? AND b.deleted_at IS NULL
  `).get(req.params.id, req.user.id);
  if (!bill) return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));
  res.json(bill);
});

// ── POST /api/bills ───────────────────────────────────────────────────────────
router.post('/', (req, res) => {
  const db = getDb();
  const {
    name, category_id, due_day, override_due_date, expected_amount, interest_rate,
    billing_cycle, autopay_enabled, autodraft_status, website, username,
    account_info, has_2fa, notes, history_visibility, cycle_type, cycle_day,
  } = req.body;

  // Validate and normalize bill data
  const validation = validateBillData(req.body);
  if (validation.errors.length > 0) {
    const firstError = validation.errors[0];
    return res.status(400).json(standardizeError(firstError.message, 'VALIDATION_ERROR', firstError.field));
  }

  const { normalized } = validation;

  // Validate category_id exists for this user
  if (normalized.category_id && !db.prepare('SELECT id FROM categories WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(normalized.category_id, req.user.id)) {
    return res.status(400).json(standardizeError('category_id is invalid for this user', 'VALIDATION_ERROR', 'category_id'));
  }

  const result = db.prepare(`
    INSERT INTO bills
      (user_id, name, category_id, due_day, override_due_date, bucket, expected_amount,
       interest_rate, billing_cycle, autopay_enabled, autodraft_status, website, username,
       account_info, has_2fa, notes, history_visibility, active, cycle_type, cycle_day,
       current_balance, minimum_payment, snowball_order, snowball_include, snowball_exempt)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?)
  `).run(
    req.user.id,
    normalized.name,
    normalized.category_id,
    normalized.due_day,
    normalized.override_due_date,
    normalized.bucket,
    normalized.expected_amount,
    normalized.interest_rate,
    normalized.billing_cycle,
    normalized.autopay_enabled,
    normalized.autodraft_status,
    normalized.website,
    normalized.username,
    normalized.account_info,
    normalized.has_2fa,
    normalized.notes,
    normalized.history_visibility,
    normalized.cycle_type,
    normalized.cycle_day,
    normalized.current_balance,
    normalized.minimum_payment,
    normalized.snowball_order,
    normalized.snowball_include,
    normalized.snowball_exempt,
  );

  const created = db.prepare('SELECT * FROM bills WHERE id = ?').get(result.lastInsertRowid);
  res.status(201).json(created);
});

// ── PUT /api/bills/:id ────────────────────────────────────────────────────────
router.put('/:id', (req, res) => {
  const db = getDb();
  const existing = db.prepare('SELECT * FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(req.params.id, req.user.id);
  if (!existing) return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));

  // Validate and normalize bill data
  const validation = validateBillData(req.body, existing);
  if (validation.errors.length > 0) {
    const firstError = validation.errors[0];
    return res.status(400).json(standardizeError(firstError.message, 'VALIDATION_ERROR', firstError.field));
  }

  const { normalized } = validation;

  // Validate category_id exists for this user if changed
  if (normalized.category_id && !db.prepare('SELECT id FROM categories WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(normalized.category_id, req.user.id)) {
    return res.status(400).json(standardizeError('category_id is invalid for this user', 'VALIDATION_ERROR', 'category_id'));
  }

  db.prepare(`
    UPDATE bills SET
      name = ?, category_id = ?, due_day = ?, override_due_date = ?, bucket = ?,
      expected_amount = ?, interest_rate = ?, billing_cycle = ?, autopay_enabled = ?, autodraft_status = ?,
      website = ?, username = ?, account_info = ?, has_2fa = ?, notes = ?, active = ?,
      history_visibility = ?, cycle_type = ?, cycle_day = ?,
      current_balance = ?, minimum_payment = ?, snowball_order = ?, snowball_include = ?, snowball_exempt = ?,
      updated_at = datetime('now')
    WHERE id = ? AND user_id = ?
  `).run(
    normalized.name,
    normalized.category_id,
    normalized.due_day,
    normalized.override_due_date,
    normalized.bucket,
    normalized.expected_amount,
    normalized.interest_rate,
    normalized.billing_cycle,
    normalized.autopay_enabled,
    normalized.autodraft_status,
    normalized.website,
    normalized.username,
    normalized.account_info,
    normalized.has_2fa,
    normalized.notes,
    normalized.active,
    normalized.history_visibility,
    normalized.cycle_type,
    normalized.cycle_day,
    normalized.current_balance,
    normalized.minimum_payment,
    normalized.snowball_order,
    normalized.snowball_include,
    normalized.snowball_exempt,
    req.params.id,
    req.user.id,
  );

  const updated = db.prepare('SELECT * FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(req.params.id, req.user.id);
  res.json(updated);
});

// ── DELETE /api/bills/:id — soft delete for 30-day recovery ───────────────────
router.delete('/:id', (req, res) => {
  const db = getDb();
  const bill = db.prepare('SELECT id, name FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(req.params.id, req.user.id);
  if (!bill) return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));

  db.prepare("UPDATE bills SET deleted_at = datetime('now'), active = 0, updated_at = datetime('now') WHERE id = ? AND user_id = ?")
    .run(req.params.id, req.user.id);

  res.json({
    success: true,
    deleted_bill_id:   bill.id,
    deleted_bill_name: bill.name,
    recoverable_until_days: 30,
  });
});

// POST /api/bills/:id/restore — undo bill soft delete
router.post('/:id/restore', (req, res) => {
  const db = getDb();
  const bill = db.prepare('SELECT id, name FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NOT NULL').get(req.params.id, req.user.id);
  if (!bill) return res.status(404).json(standardizeError('Deleted bill not found', 'NOT_FOUND', 'bill_id'));

  db.prepare("UPDATE bills SET deleted_at = NULL, active = 1, updated_at = datetime('now') WHERE id = ? AND user_id = ?")
    .run(req.params.id, req.user.id);

  res.json(db.prepare('SELECT * FROM bills WHERE id = ? AND user_id = ?').get(req.params.id, req.user.id));
});

// ── GET /api/bills/:id/payments?page=1&limit=20 ───────────────────────────────
router.get('/:id/payments', (req, res) => {
  const db = getDb();
  const bill = db.prepare('SELECT id, name FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(req.params.id, req.user.id);
  if (!bill) return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));

  const limit  = Math.min(parseInt(req.query.limit || '20', 10), 100);
  const page   = Math.max(parseInt(req.query.page  || '1',  10), 1);
  const offset = (page - 1) * limit;

  const total = db.prepare(
    'SELECT COUNT(*) AS n FROM payments WHERE bill_id = ? AND deleted_at IS NULL'
  ).get(req.params.id).n;

  const items = db.prepare(
    'SELECT * FROM payments WHERE bill_id = ? AND deleted_at IS NULL ORDER BY paid_date DESC LIMIT ? OFFSET ?'
  ).all(req.params.id, limit, offset);

  res.json({
    bill_id:    parseInt(req.params.id, 10),
    bill_name:  bill.name,
    total,
    page,
    limit,
    pages:      Math.ceil(total / limit),
    payments:   items,
  });
});

// ── POST /api/bills/:id/toggle-paid — toggle Paid/Unpaid status ──────────────
router.post('/:id/toggle-paid', (req, res) => {
  const db = getDb();
  const billId = parseInt(req.params.id, 10);

  // Get bill - always scope to the requesting user
  const bill = db.prepare('SELECT id, expected_amount, user_id, due_day, current_balance, interest_rate FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(billId, req.user.id);

  if (!bill) return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));

  // Scope to year/month if provided
  const year  = req.body.year  !== undefined ? parseInt(req.body.year,  10) : null;
  const month = req.body.month !== undefined ? parseInt(req.body.month, 10) : null;
  if ((year === null) !== (month === null)) {
    return res.status(400).json(standardizeError('year and month must both be provided or both omitted', 'VALIDATION_ERROR', 'year'));
  }
  if (year !== null && (Number.isNaN(year) || year < 2000 || year > 2100)) {
    return res.status(400).json(standardizeError('year must be a 4-digit integer between 2000 and 2100', 'VALIDATION_ERROR', 'year'));
  }
  if (month !== null && (Number.isNaN(month) || month < 1 || month > 12)) {
    return res.status(400).json(standardizeError('month must be an integer between 1 and 12', 'VALIDATION_ERROR', 'month'));
  }

  let currentPayment;
  if (year !== null && month !== null) {
    currentPayment = db.prepare(
      'SELECT * FROM payments WHERE bill_id = ? AND deleted_at IS NULL AND strftime(\'%Y\', paid_date) = ? AND strftime(\'%m\', paid_date) = ? ORDER BY paid_date DESC LIMIT 1'
    ).get(billId, String(year), String(month).padStart(2, '0'));
  } else {
    currentPayment = db.prepare(
      'SELECT * FROM payments WHERE bill_id = ? AND deleted_at IS NULL ORDER BY paid_date DESC LIMIT 1'
    ).get(billId);
  }

  // If paid (has payment), remove it → Unpaid
  if (currentPayment) {
    // Reverse any balance delta that was applied when this payment was created
    if (currentPayment.balance_delta != null) {
      const freshBill = db.prepare('SELECT current_balance FROM bills WHERE id = ?').get(billId);
      if (freshBill?.current_balance != null) {
        const restored = Math.max(0, Math.round((freshBill.current_balance - currentPayment.balance_delta) * 100) / 100);
        db.prepare("UPDATE bills SET current_balance = ?, updated_at = datetime('now') WHERE id = ?").run(restored, billId);
      }
    }
    db.prepare("UPDATE payments SET deleted_at = datetime('now') WHERE id = ?").run(currentPayment.id);
    res.json({
      success: true,
      isPaid: false,
      action: 'removed_payment',
      paymentId: currentPayment.id,
    });
    return;
  }

  // If unpaid, create payment → Paid
  // Use expected_amount if no amount provided
  const amount = req.body.amount !== undefined ? req.body.amount : bill.expected_amount;
  
  // Determine paid_date
  let paidDate = req.body.paid_date;
  if (!paidDate && year !== null && month !== null) {
    // Calculate paid_date from bill's due_day clamped to the month's days
    const daysInMonth = new Date(year, month, 0).getDate();
    const day = Math.min(Math.max(Number(bill.due_day), 1), daysInMonth);
    paidDate = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
  } else if (!paidDate) {
    paidDate = new Date().toISOString().slice(0, 10);
  }
  
  const method = req.body.method || null;
  const notes = req.body.notes || null;

  const paymentValidation = validatePaymentInput(
    { amount, paid_date: paidDate },
    { requireBillId: false },
  );
  if (paymentValidation.error) {
    return res.status(400).json(standardizeError(paymentValidation.error, 'VALIDATION_ERROR', paymentValidation.field));
  }
  const payment = paymentValidation.normalized;

  // Compute balance delta for debt bills before inserting
  const balCalc = computeBalanceDelta(bill, payment.amount);

  const result = db.prepare(
    'INSERT INTO payments (bill_id, amount, paid_date, method, notes, balance_delta) VALUES (?, ?, ?, ?, ?, ?)'
  ).run(billId, payment.amount, payment.paid_date, method, notes, balCalc?.balance_delta ?? null);

  if (balCalc) {
    db.prepare("UPDATE bills SET current_balance = ?, updated_at = datetime('now') WHERE id = ?")
      .run(balCalc.new_balance, billId);
  }

  res.status(201).json({
    success: true,
    isPaid: true,
    action: 'created_payment',
    payment: db.prepare('SELECT * FROM payments WHERE id = ?').get(result.lastInsertRowid),
  });
});

// ── GET /api/bills/:id/history-ranges ────────────────────────────────────────
router.get('/:id/history-ranges', (req, res) => {
  const db = getDb();
  if (!db.prepare('SELECT id FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(req.params.id, req.user.id))
    return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));

  const ranges = db.prepare(
    'SELECT * FROM bill_history_ranges WHERE bill_id = ? ORDER BY start_year ASC, start_month ASC'
  ).all(req.params.id);

  const bill = db.prepare('SELECT history_visibility FROM bills WHERE id = ? AND deleted_at IS NULL').get(req.params.id);

  res.json({ bill_id: parseInt(req.params.id, 10), history_visibility: bill.history_visibility, ranges });
});

// ── POST /api/bills/:id/history-ranges ───────────────────────────────────────
router.post('/:id/history-ranges', (req, res) => {
  const db = getDb();
  if (!db.prepare('SELECT id FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(req.params.id, req.user.id))
    return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));

  const { start_year, start_month, end_year, end_month, label } = req.body;

  const sy = parseInt(start_year, 10);
  const sm = parseInt(start_month, 10);
  if (isNaN(sy) || sy < 2000 || sy > 2100)
    return res.status(400).json(standardizeError('start_year must be between 2000 and 2100', 'VALIDATION_ERROR', 'start_year'));
  if (isNaN(sm) || sm < 1 || sm > 12)
    return res.status(400).json(standardizeError('start_month must be between 1 and 12', 'VALIDATION_ERROR', 'start_month'));

  let ey = null, em = null;
  if (end_year != null) {
    ey = parseInt(end_year, 10);
    if (isNaN(ey) || ey < 2000 || ey > 2100)
      return res.status(400).json(standardizeError('end_year must be between 2000 and 2100', 'VALIDATION_ERROR', 'end_year'));
  }
  if (end_month != null) {
    em = parseInt(end_month, 10);
    if (isNaN(em) || em < 1 || em > 12)
      return res.status(400).json(standardizeError('end_month must be between 1 and 12', 'VALIDATION_ERROR', 'end_month'));
  }
  if ((ey == null) !== (em == null)) {
    return res.status(400).json(standardizeError('end_year and end_month must both be provided or both omitted', 'VALIDATION_ERROR', 'end_year'));
  }
  if (ey != null) {
    const startVal = sy * 12 + sm;
    const endVal   = ey * 12 + em;
    if (endVal < startVal)
      return res.status(400).json(standardizeError('end date must be on or after start date', 'VALIDATION_ERROR', 'end_year'));
  }

  const result = db.prepare(`
    INSERT INTO bill_history_ranges (bill_id, start_year, start_month, end_year, end_month, label)
    VALUES (?, ?, ?, ?, ?, ?)
  `).run(req.params.id, sy, sm, ey, em, label || null);

  const created = db.prepare('SELECT * FROM bill_history_ranges WHERE id = ?').get(result.lastInsertRowid);
  res.status(201).json(created);
});

// ── PUT /api/bills/:id/history-ranges/:rangeId ───────────────────────────────
router.put('/:id/history-ranges/:rangeId', (req, res) => {
  const db = getDb();
  if (!db.prepare('SELECT id FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(req.params.id, req.user.id))
    return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));

  const range = db.prepare('SELECT * FROM bill_history_ranges WHERE id = ? AND bill_id = ?')
    .get(req.params.rangeId, req.params.id);
  if (!range) return res.status(404).json(standardizeError('History range not found', 'NOT_FOUND', 'rangeId'));

  const { start_year, start_month, end_year, end_month, label } = req.body;

  const sy = start_year != null ? parseInt(start_year, 10) : range.start_year;
  const sm = start_month != null ? parseInt(start_month, 10) : range.start_month;
  if (isNaN(sy) || sy < 2000 || sy > 2100)
    return res.status(400).json(standardizeError('start_year must be between 2000 and 2100', 'VALIDATION_ERROR', 'start_year'));
  if (isNaN(sm) || sm < 1 || sm > 12)
    return res.status(400).json(standardizeError('start_month must be between 1 and 12', 'VALIDATION_ERROR', 'start_month'));

  let ey = range.end_year;
  let em = range.end_month;
  if (end_year !== undefined) ey = end_year != null ? parseInt(end_year, 10) : null;
  if (end_month !== undefined) em = end_month != null ? parseInt(end_month, 10) : null;

  if (ey != null && (isNaN(ey) || ey < 2000 || ey > 2100))
    return res.status(400).json(standardizeError('end_year must be between 2000 and 2100', 'VALIDATION_ERROR', 'end_year'));
  if (em != null && (isNaN(em) || em < 1 || em > 12))
    return res.status(400).json(standardizeError('end_month must be between 1 and 12', 'VALIDATION_ERROR', 'end_month'));
  if ((ey == null) !== (em == null))
    return res.status(400).json(standardizeError('end_year and end_month must both be provided or both omitted', 'VALIDATION_ERROR', 'end_year'));
  if (ey != null && (ey * 12 + em) < (sy * 12 + sm))
    return res.status(400).json(standardizeError('end date must be on or after start date', 'VALIDATION_ERROR', 'end_year'));

  db.prepare(`
    UPDATE bill_history_ranges
    SET start_year = ?, start_month = ?, end_year = ?, end_month = ?, label = ?,
        updated_at = datetime('now')
    WHERE id = ? AND bill_id = ?
  `).run(sy, sm, ey, em, label !== undefined ? (label || null) : range.label, req.params.rangeId, req.params.id);

  const updated = db.prepare('SELECT * FROM bill_history_ranges WHERE id = ?').get(req.params.rangeId);
  res.json(updated);
});

// ── DELETE /api/bills/:id/history-ranges/:rangeId ────────────────────────────
router.delete('/:id/history-ranges/:rangeId', (req, res) => {
  const db = getDb();
  if (!db.prepare('SELECT id FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(req.params.id, req.user.id))
    return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));

  const range = db.prepare('SELECT id FROM bill_history_ranges WHERE id = ? AND bill_id = ?')
    .get(req.params.rangeId, req.params.id);
  if (!range) return res.status(404).json(standardizeError('History range not found', 'NOT_FOUND', 'rangeId'));

  db.prepare('DELETE FROM bill_history_ranges WHERE id = ? AND bill_id = ?')
    .run(req.params.rangeId, req.params.id);

  res.json({ success: true });
});

// ── GET /api/bills/:id/amortization — full month-by-month schedule for a debt bill ──
router.get('/:id/amortization', (req, res) => {
  const db     = getDb();
  const billId = parseInt(req.params.id, 10);
  const bill   = db.prepare('SELECT * FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(billId, req.user.id);
  if (!bill) return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));

  const balance  = Number(bill.current_balance);
  const apr      = Number(bill.interest_rate) || 0;
  const minPmt   = Number(bill.minimum_payment) || 0;

  // Optional override: ?payment=X lets callers model "what if I pay more?"
  let payment = minPmt;
  if (req.query.payment !== undefined) {
    const qp = parseFloat(req.query.payment);
    if (!Number.isFinite(qp) || qp <= 0) {
      return res.status(400).json(standardizeError('payment must be a positive number', 'VALIDATION_ERROR', 'payment'));
    }
    payment = qp;
  }

  // Optional ?max_months=N (default 360, hard cap 600)
  let maxMonths = 360;
  if (req.query.max_months !== undefined) {
    const qm = parseInt(req.query.max_months, 10);
    if (Number.isInteger(qm) && qm > 0) maxMonths = Math.min(qm, 600);
  }

  if (!Number.isFinite(balance) || balance <= 0) {
    return res.json({ bill_id: billId, schedule: [], apr_snapshot: null, error: 'No current balance set' });
  }

  const schedule    = amortizationSchedule(balance, apr, payment, maxMonths);
  const apr_snapshot = debtAprSnapshot(bill);
  const total_interest = schedule.reduce((s, r) => s + r.interest, 0);

  res.json({
    bill_id:       billId,
    balance,
    apr,
    payment,
    schedule,
    summary: {
      months:          schedule.length,
      total_interest:  Math.round(total_interest * 100) / 100,
      total_paid:      Math.round((schedule.reduce((s, r) => s + r.payment, 0)) * 100) / 100,
      capped:          schedule.length >= maxMonths && schedule[schedule.length - 1]?.balance > 0,
    },
    apr_snapshot,
  });
});

// ── PATCH /api/bills/:id/snowball — update only snowball_include / snowball_exempt ──
router.patch('/:id/snowball', (req, res) => {
  const db = getDb();
  const billId = parseInt(req.params.id, 10);
  if (!db.prepare('SELECT id FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(billId, req.user.id)) {
    return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));
  }
  const include = req.body.snowball_include !== undefined ? (req.body.snowball_include ? 1 : 0) : undefined;
  const exempt  = req.body.snowball_exempt  !== undefined ? (req.body.snowball_exempt  ? 1 : 0) : undefined;
  const parts = [];
  const vals  = [];
  if (include !== undefined) { parts.push('snowball_include = ?'); vals.push(include); }
  if (exempt  !== undefined) { parts.push('snowball_exempt = ?');  vals.push(exempt);  }
  if (parts.length === 0) return res.status(400).json(standardizeError('Nothing to update', 'VALIDATION_ERROR'));
  parts.push("updated_at = datetime('now')");
  db.prepare(`UPDATE bills SET ${parts.join(', ')} WHERE id = ? AND user_id = ?`).run(...vals, billId, req.user.id);
  res.json({ id: billId, snowball_include: include, snowball_exempt: exempt });
});

// ── PATCH /api/bills/:id/balance — lightweight balance-only update ────────────
router.patch('/:id/balance', (req, res) => {
  const db = getDb();
  const billId = parseInt(req.params.id, 10);
  if (!db.prepare('SELECT id FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(billId, req.user.id)) {
    return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));
  }

  const raw = req.body.current_balance;
  let val = null;
  if (raw !== null && raw !== '' && raw !== undefined) {
    val = parseFloat(raw);
    if (!Number.isFinite(val) || val < 0) {
      return res.status(400).json(standardizeError('current_balance must be a non-negative number', 'VALIDATION_ERROR', 'current_balance'));
    }
    val = Math.round(val * 100) / 100;
  }

  db.prepare("UPDATE bills SET current_balance = ?, updated_at = datetime('now') WHERE id = ?").run(val, billId);
  res.json({ id: billId, current_balance: val });
});

module.exports = router;
