'use strict';

const { getDb } = require('../db/database');
const { accountingActiveSql } = require('./paymentAccountingService');
const { sumMoney, fromCents } = require('../utils/money.mts');
const { resolveDueDate } = require('./statusService');

function parseInteger(value, fallback) {
  if (value === undefined || value === null || value === '') return fallback;
  const parsed = Number(value);
  return Number.isInteger(parsed) ? parsed : NaN;
}

function monthKey(year, month) {
  return `${year}-${String(month).padStart(2, '0')}`;
}

function monthLabel(year, month) {
  return new Date(Date.UTC(year, month - 1, 1)).toLocaleString('en-US', {
    month: 'short',
    year: '2-digit',
    timeZone: 'UTC',
  });
}

function addMonths(year, month, delta) {
  const date = new Date(Date.UTC(year, month - 1 + delta, 1));
  return { year: date.getUTCFullYear(), month: date.getUTCMonth() + 1 };
}

function monthEndDate(year, month) {
  const day = new Date(Date.UTC(year, month, 0)).getUTCDate();
  return `${monthKey(year, month)}-${String(day).padStart(2, '0')}`;
}

function buildMonths(endYear, endMonth, count) {
  return Array.from({ length: count }, (_, index) => {
    const value = addMonths(endYear, endMonth, index - count + 1);
    return {
      ...value,
      key: monthKey(value.year, value.month),
      label: monthLabel(value.year, value.month),
      start: `${monthKey(value.year, value.month)}-01`,
      end: monthEndDate(value.year, value.month),
    };
  });
}

function validateSummaryQuery(query, now = new Date()) {
  const year = parseInteger(query.year, now.getFullYear());
  const month = parseInteger(query.month, now.getMonth() + 1);
  const months = parseInteger(query.months, 12);
  const categoryId = parseInteger(query.category_id, null);
  const billId = parseInteger(query.bill_id, null);
  const includeInactive = query.include_inactive === 'true';
  const includeSkipped = query.include_skipped !== 'false';

  if (!Number.isInteger(year) || year < 2000 || year > 2100) {
    return { error: 'year must be a 4-digit integer between 2000 and 2100' };
  }
  if (!Number.isInteger(month) || month < 1 || month > 12) {
    return { error: 'month must be an integer between 1 and 12' };
  }
  if (!Number.isInteger(months) || months < 1 || months > 36) {
    return { error: 'months must be an integer between 1 and 36' };
  }
  if (categoryId !== null && (!Number.isInteger(categoryId) || categoryId < 1)) {
    return { error: 'category_id must be a positive integer' };
  }
  if (billId !== null && (!Number.isInteger(billId) || billId < 1)) {
    return { error: 'bill_id must be a positive integer' };
  }

  return { year, month, months, categoryId, billId, includeInactive, includeSkipped };
}

function isMonthInPast(year, month) {
  const now = new Date();
  const currentMonthStart = new Date(now.getFullYear(), now.getMonth(), 1);
  const targetMonthStart = new Date(year, month - 1, 1);
  return targetMonthStart < currentMonthStart;
}

function buildBillWhere({ userId, categoryId, billId, includeInactive }) {
  const clauses = ['b.user_id = ?', 'b.deleted_at IS NULL'];
  const params = [userId];
  if (!includeInactive) clauses.push('b.active = 1');
  if (categoryId) {
    clauses.push('b.category_id = ?');
    params.push(categoryId);
  }
  if (billId) {
    clauses.push('b.id = ?');
    params.push(billId);
  }
  return { where: clauses.join(' AND '), params };
}

function emptySummary(parsed, rangeMonths, startDate, endDate, categories) {
  return {
    range: { year: parsed.year, month: parsed.month, months: parsed.months, start: startDate, end: endDate },
    filters: {
      category_id: parsed.categoryId,
      bill_id: parsed.billId,
      include_inactive: parsed.includeInactive,
      include_skipped: parsed.includeSkipped,
    },
    categories,
    bills: [],
    monthly_spending: [],
    expected_vs_actual: [],
    category_spend: [],
    heatmap: { months: rangeMonths.map(({ key, label, year, month }) => ({ key, label, year, month })), rows: [] },
    generated_at: new Date().toISOString(),
  };
}

function getAnalyticsSummary(userId, query = {}) {
  const parsed = validateSummaryQuery(query);
  if (parsed.error) return parsed;

  const db = getDb();
  const rangeMonths = buildMonths(parsed.year, parsed.month, parsed.months);
  const startDate = rangeMonths[0].start;
  const endDate = rangeMonths[rangeMonths.length - 1].end;
  const billWhere = buildBillWhere({ ...parsed, userId });

  const categories = db.prepare(`
    SELECT id, name
    FROM categories
    WHERE user_id = ?
      AND deleted_at IS NULL
    ORDER BY name COLLATE NOCASE
  `).all(userId);

  const bills = db.prepare(`
    SELECT b.id, b.name, b.category_id, b.expected_amount, b.active, b.created_at,
           b.due_day, b.billing_cycle, b.cycle_type, b.cycle_day,
           c.name AS category_name
    FROM bills b
    LEFT JOIN categories c ON c.id = b.category_id AND c.user_id = b.user_id AND c.deleted_at IS NULL
    WHERE ${billWhere.where}
    ORDER BY b.name COLLATE NOCASE
  `).all(...billWhere.params);

  if (!bills.length) {
    return emptySummary(parsed, rangeMonths, startDate, endDate, categories);
  }

  const billIds = bills.map(b => b.id);
  const placeholders = billIds.map(() => '?').join(',');

  const paymentRows = db.prepare(`
    SELECT p.bill_id,
           substr(p.paid_date, 1, 7) AS month_key,
           SUM(p.amount) AS total
    FROM payments p
    JOIN bills b ON b.id = p.bill_id
    WHERE b.user_id = ?
      AND b.deleted_at IS NULL
      AND p.bill_id IN (${placeholders})
      AND p.paid_date BETWEEN ? AND ?
      AND p.deleted_at IS NULL
      AND ${accountingActiveSql('p')}
    GROUP BY p.bill_id, substr(p.paid_date, 1, 7)
  `).all(userId, ...billIds, startDate, endDate);

  const stateRows = db.prepare(`
    SELECT m.bill_id, m.year, m.month, m.actual_amount, m.is_skipped
    FROM monthly_bill_state m
    JOIN bills b ON b.id = m.bill_id
    WHERE b.user_id = ?
      AND b.deleted_at IS NULL
      AND m.bill_id IN (${placeholders})
      AND (m.year * 100 + m.month) BETWEEN ? AND ?
  `).all(
    userId,
    ...billIds,
    rangeMonths[0].year * 100 + rangeMonths[0].month,
    rangeMonths[rangeMonths.length - 1].year * 100 + rangeMonths[rangeMonths.length - 1].month,
  );

  const paymentByBillMonth = new Map(paymentRows.map(row => [`${row.bill_id}:${row.month_key}`, Number(row.total) || 0]));
  const stateByBillMonth = new Map(stateRows.map(row => [`${row.bill_id}:${monthKey(row.year, row.month)}`, row]));

  const monthly_spending = rangeMonths.map(m => {
    const total = sumMoney(bills, bill => paymentByBillMonth.get(`${bill.id}:${m.key}`) || 0);
    return { month: m.key, label: m.label, total: fromCents(total) };
  }).filter(row => row.total > 0);

  const expected_vs_actual = rangeMonths.map(m => {
    const [mYear, mMonth] = m.key.split('-').map(Number);
    let expected = 0;
    let actual = 0;
    let skipped_count = 0;
    for (const bill of bills) {
      const state = stateByBillMonth.get(`${bill.id}:${m.key}`);
      const skipped = !!state?.is_skipped;
      if (skipped) skipped_count += 1;
      if (!skipped || parsed.includeSkipped) {
        actual += paymentByBillMonth.get(`${bill.id}:${m.key}`) || 0;
      }
      // QA-B5-01 family: only add "expected" in months the bill actually occurs,
      // so annual / off-month quarterly bills don't inflate the expected line
      // every month (matches the Tracker / Summary occurrence gate).
      if (!skipped && resolveDueDate(bill, mYear, mMonth)) {
        expected += state?.actual_amount ?? bill.expected_amount ?? 0;
      }
    }
    return {
      month: m.key,
      label: m.label,
      expected: fromCents(expected),
      actual: fromCents(actual),
      skipped_count,
    };
  }).filter(row => row.expected > 0 || row.actual > 0 || row.skipped_count > 0);

  const categoryMap = new Map();
  for (const bill of bills) {
    const categoryId = bill.category_id || null;
    const key = categoryId == null ? 'uncategorized' : String(categoryId);
    const existing = categoryMap.get(key) || {
      category_id: categoryId,
      category_name: bill.category_name || 'Uncategorized',
      total: 0,
    };
    for (const m of rangeMonths) {
      existing.total += paymentByBillMonth.get(`${bill.id}:${m.key}`) || 0;
    }
    categoryMap.set(key, existing);
  }
  const category_spend = Array.from(categoryMap.values())
    .map(row => ({ ...row, total: fromCents(row.total) }))
    .filter(row => row.total > 0)
    .sort((a, b) => b.total - a.total);

  const heatmapRows = bills.map(bill => {
    const cells = rangeMonths.map(m => {
      const paid = (paymentByBillMonth.get(`${bill.id}:${m.key}`) || 0) > 0;
      const state = stateByBillMonth.get(`${bill.id}:${m.key}`);
      const skipped = !!state?.is_skipped;
      let status = 'no_data';
      if (skipped) status = 'skipped';
      else if (paid) status = 'paid';
      else if (isMonthInPast(m.year, m.month)) status = 'missed';
      return {
        month: m.key,
        label: m.label,
        status,
        amount_paid: fromCents(paymentByBillMonth.get(`${bill.id}:${m.key}`) || 0),
      };
    });
    return {
      bill_id: bill.id,
      bill_name: bill.name,
      category_name: bill.category_name || 'Uncategorized',
      active: !!bill.active,
      cells: parsed.includeSkipped ? cells : cells.filter(cell => cell.status !== 'skipped'),
    };
  });

  return {
    range: { year: parsed.year, month: parsed.month, months: parsed.months, start: startDate, end: endDate },
    filters: {
      category_id: parsed.categoryId,
      bill_id: parsed.billId,
      include_inactive: parsed.includeInactive,
      include_skipped: parsed.includeSkipped,
    },
    categories,
    bills: bills.map(b => ({
      id: b.id,
      name: b.name,
      category_id: b.category_id,
      category_name: b.category_name || 'Uncategorized',
      active: !!b.active,
    })),
    monthly_spending,
    expected_vs_actual,
    category_spend,
    heatmap: {
      months: rangeMonths.map(({ key, label, year, month }) => ({ key, label, year, month })),
      rows: heatmapRows,
    },
    generated_at: new Date().toISOString(),
  };
}

module.exports = {
  addMonths,
  buildMonths,
  getAnalyticsSummary,
  monthEndDate,
  monthKey,
  monthLabel,
  validateSummaryQuery,
};
