'use strict';

const { getDb } = require('../db/database');
const { getCycleRange } = require('./statusService');
const { accountingActiveSql } = require('./paymentAccountingService');
const { getUserSettings } = require('./userSettings');
const { localDateString } = require('../utils/dates.mts');
const { roundMoney, fromCents } = require('../utils/money.mts');

const MONTHS_BACK  = 3;
const MIN_PAID_MONTHS = 2;
const MIN_ABS_DELTA   = 1.00;

function median(arr) {
  if (!arr.length) return 0;
  const sorted = [...arr].sort((a, b) => a - b);
  const mid = Math.floor(sorted.length / 2);
  return sorted.length % 2 !== 0
    ? sorted[mid]
    : (sorted[mid - 1] + sorted[mid]) / 2;
}

function monthEnd(year, month) {
  return new Date(Date.UTC(year, month, 0)).getUTCDate();
}

function getDriftReport(userId, now = new Date()) {
  try {
    const db = getDb();
    const settings = getUserSettings(userId);
    const thresholdPct = Math.max(1, Math.min(25,
      parseFloat(settings.drift_threshold_pct ?? '5') || 5
    ));

    const bills = db.prepare(`
      SELECT b.*, c.name AS category_name
      FROM bills b
      LEFT JOIN categories c ON c.id = b.category_id
      WHERE b.user_id = ? AND b.active = 1 AND b.deleted_at IS NULL
    `).all(userId);

    const todayStr = localDateString(now);
    const drifted  = [];

    const mbsStmt = db.prepare(
      'SELECT is_skipped FROM monthly_bill_state WHERE bill_id = ? AND year = ? AND month = ?'
    );
    const payStmt = db.prepare(`
      SELECT COALESCE(SUM(amount), 0) AS total
      FROM payments
      WHERE bill_id = ? AND paid_date BETWEEN ? AND ? AND deleted_at IS NULL
        AND ${accountingActiveSql()}
    `);

    for (const bill of bills) {
      const expectedAmount = fromCents(bill.expected_amount);
      if (!expectedAmount || expectedAmount <= 0) continue;
      if (bill.drift_snoozed_until && bill.drift_snoozed_until > todayStr) continue;

      const monthTotals = [];

      for (let i = 1; i <= MONTHS_BACK; i++) {
        const d  = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - i, 1));
        const yr = d.getUTCFullYear();
        const mo = d.getUTCMonth() + 1;

        // Skip if bill was created after this month ended
        const monthEndStr = `${yr}-${String(mo).padStart(2,'0')}-${String(monthEnd(yr, mo)).padStart(2,'0')}`;
        if (bill.created_at && bill.created_at.slice(0, 10) > monthEndStr) continue;

        const mbs = mbsStmt.get(bill.id, yr, mo);
        if (mbs?.is_skipped) continue;

        const range = getCycleRange(yr, mo, bill);
        if (!range) continue;

        const { total } = payStmt.get(bill.id, range.start, range.end);
        if (total > 0) monthTotals.push(fromCents(total));
      }

      if (monthTotals.length < MIN_PAID_MONTHS) continue;

      const recentAmount = median(monthTotals);
      const delta        = recentAmount - expectedAmount;
      const absDelta     = Math.abs(delta);
      const driftPct     = (delta / expectedAmount) * 100;

      if (absDelta < MIN_ABS_DELTA) continue;
      if (Math.abs(driftPct) < thresholdPct) continue;

      drifted.push({
        id:               bill.id,
        name:             bill.name,
        category_name:    bill.category_name ?? null,
        expected_amount:  expectedAmount,
        recent_amount:    roundMoney(recentAmount),
        drift_pct:        Math.round(driftPct * 10) / 10,
        direction:        delta > 0 ? 'up' : 'down',
        months_sampled:   monthTotals.length,
        drift_snoozed_until: bill.drift_snoozed_until ?? null,
      });
    }

    return { bills: drifted, threshold_pct: thresholdPct };
  } catch (err) {
    console.error('[driftService] getDriftReport error:', err.message);
    return { bills: [], threshold_pct: 5, error: err.message };
  }
}

module.exports = { getDriftReport };
