const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');

const dbPath = path.join(os.tmpdir(), `bill-tracker-spending-summary-test-${process.pid}.sqlite`);
process.env.DB_PATH = dbPath;

const { getDb, closeDb } = require('../db/database');
const { getSpendingSummary, setSpendingBudget } = require('../services/spendingService');

function createUser(db, suffix) {
  return db.prepare(`
    INSERT INTO users (username, password_hash, role, active, email, created_at, updated_at)
    VALUES (?, 'x', 'user', 1, ?, datetime('now'), datetime('now'))
  `).run(`spending-summary-${suffix}`, `spending-summary-${suffix}@local`).lastInsertRowid;
}

function createCategory(db, userId, name, { spendingEnabled = 1, groupId = null } = {}) {
  return db.prepare(`
    INSERT INTO categories (user_id, name, spending_enabled, group_id)
    VALUES (?, ?, ?, ?)
  `).run(userId, name, spendingEnabled, groupId).lastInsertRowid;
}

function createTransaction(db, userId, { amountCents, postedDate, categoryId = null, matchStatus = 'unmatched' }) {
  return db.prepare(`
    INSERT INTO transactions (user_id, source_type, posted_date, amount, payee, match_status, ignored, spending_category_id)
    VALUES (?, 'manual', ?, ?, 'Test Payee', ?, 0, ?)
  `).run(userId, postedDate, amountCents, matchStatus, categoryId).lastInsertRowid;
}

test.after(() => {
  closeDb();
  for (const suffix of ['', '-wal', '-shm']) {
    fs.rmSync(`${dbPath}${suffix}`, { force: true });
  }
});

test('a budgeted category with no activity still appears with its budget', () => {
  const db = getDb();
  const userId = createUser(db, 'zero-activity');
  const groceries = createCategory(db, userId, 'Groceries');
  setSpendingBudget(db, userId, groceries, 2026, 1, 300);

  const summary = getSpendingSummary(db, userId, 2026, 1);
  const row = summary.by_category.find(c => c.category_id === groceries);

  assert.ok(row, 'budgeted category should appear even with $0 activity');
  assert.equal(row.amount, 0);
  assert.equal(row.budget, 300);
  assert.equal(row.tx_count, 0);
});

test('outflows assigned to a non-spending category are bundled into "Other categories"', () => {
  const db = getDb();
  const userId = createUser(db, 'other-bucket');
  const nonSpending = createCategory(db, userId, 'Transfers', { spendingEnabled: 0 });
  createTransaction(db, userId, { amountCents: -1500, postedDate: '2026-02-05', categoryId: nonSpending });

  const summary = getSpendingSummary(db, userId, 2026, 2);
  const other = summary.by_category.find(c => c.category_id === 'other');

  assert.ok(other, '"Other categories" row should appear');
  assert.equal(other.amount, 15);
  assert.equal(other.tx_count, 1);
  assert.equal(summary.total_spending, 15);
});

test('avg_3mo reflects the average spend over the prior 3 months', () => {
  const db = getDb();
  const userId = createUser(db, 'avg-3mo');
  const dining = createCategory(db, userId, 'Dining');

  createTransaction(db, userId, { amountCents: -1000, postedDate: '2026-03-10', categoryId: dining });
  createTransaction(db, userId, { amountCents: -2000, postedDate: '2026-04-10', categoryId: dining });
  createTransaction(db, userId, { amountCents: -3000, postedDate: '2026-05-10', categoryId: dining });

  const summary = getSpendingSummary(db, userId, 2026, 6);
  const row = summary.by_category.find(c => c.category_id === dining);

  assert.ok(row);
  assert.equal(row.avg_3mo, 20); // (10 + 20 + 30) / 3
});

test('category_groups are returned and group_id/group_name are attached to categories', () => {
  const db = getDb();
  const userId = createUser(db, 'groups');

  const groupRow = db.prepare(`
    INSERT INTO category_groups (user_id, name, sort_order) VALUES (?, 'Bills', 0)
  `).run(userId);
  const groupId = groupRow.lastInsertRowid;

  const rent = createCategory(db, userId, 'Rent', { groupId });
  createTransaction(db, userId, { amountCents: -120000, postedDate: '2026-07-01', categoryId: rent });

  const summary = getSpendingSummary(db, userId, 2026, 7);
  const row = summary.by_category.find(c => c.category_id === rent);

  assert.deepEqual(summary.category_groups, [{ id: groupId, name: 'Bills', sort_order: 0 }]);
  assert.equal(row.group_id, groupId);
  assert.equal(row.group_name, 'Bills');
});
