'use strict';

import type { Dollars } from '../utils/money.mts';

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

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

interface DriftBillRow {
  id: any;
  name: any;
  category_name: any;
  expected_amount: Dollars;
  recent_amount: Dollars;
  drift_pct: number;
  direction: 'up' | 'down';
  months_sampled: number;
  drift_snoozed_until: any;
}
interface DriftReport {
  bills: DriftBillRow[];
  threshold_pct: number;
  error?: string;
}

function median(arr: number[]): number {
  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: number, month: number): number {
  return new Date(Date.UTC(year, month, 0)).getUTCDate();
}

function getDriftReport(userId: number, now: Date = new Date()): DriftReport {
  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: DriftBillRow[] = [];

    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: number[] = [];

      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)!); // total>0 ⇒ fromCents non-null
      }

      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: any) {
    console.error('[driftService] getDriftReport error:', err.message);
    return { bills: [], threshold_pct: 5, error: err.message };
  }
}

module.exports = { getDriftReport };
