'use strict';

// Demo seed + surgical removal. seedDemoData() must populate every user-facing
// surface, and clearSeededDemoData() must remove EXACTLY the seeded rows — never a
// user's own bills/categories/planning, and leave no orphans. This is the contract
// the "Seed / Clear Demo Data" buttons depend on.
const test = require('node:test');
const assert = require('node:assert/strict');
const os = require('node:os');
const path = require('node:path');
const fs = require('node:fs');

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

const { getDb, closeDb } = require('../db/database.cts');
const { seedDemoData } = require('../scripts/seedDemoData.cts');
const { clearSeededDemoData, eraseUserData } = require('../services/userDataService.cts');

const mkUser = (db, name) =>
  db
    .prepare(
      "INSERT INTO users (username, password_hash, role, active) VALUES (?, 'hash', 'user', 1)",
    )
    .run(name).lastInsertRowid;
const num = (db, sql, ...p) => db.prepare(sql).get(...p).n;

let db, userA, userB, userC;
test.before(() => {
  db = getDb();
  db.pragma('foreign_keys = ON');
  userA = mkUser(db, 'seed-a');
  userB = mkUser(db, 'seed-b');
  userC = mkUser(db, 'seed-c');
  seedDemoData(userA);
  seedDemoData(userB);
  seedDemoData(userC);
});
test.after(() => {
  closeDb();
  for (const s of ['', '-wal', '-shm']) {
    try {
      fs.unlinkSync(dbPath + s);
    } catch {}
  }
});

test('seed populates every user-facing surface', () => {
  assert.equal(num(db, 'SELECT COUNT(*) n FROM bills WHERE user_id=? AND is_seeded=1', userA), 23);
  // 8 of 9 bill categories reuse the user's defaults (not flagged); Healthcare + 5 spending are created.
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM categories WHERE user_id=? AND is_seeded=1', userA),
    6,
  );
  assert.equal(
    num(
      db,
      'SELECT COUNT(*) n FROM categories WHERE user_id=? AND is_seeded=1 AND spending_enabled=1',
      userA,
    ),
    5,
  );
  assert.ok(
    num(
      db,
      'SELECT COUNT(*) n FROM payments p JOIN bills b ON b.id=p.bill_id WHERE b.user_id=?',
      userA,
    ) > 40,
    'payment history',
  );
  assert.ok(
    num(db, 'SELECT COUNT(*) n FROM transactions WHERE user_id=? AND is_seeded=1', userA) > 10,
    'bank transactions',
  );
  assert.ok(
    num(
      db,
      "SELECT COUNT(*) n FROM transactions WHERE user_id=? AND match_status='matched'",
      userA,
    ) >= 3,
    'matched txns',
  );
  assert.ok(
    num(
      db,
      "SELECT COUNT(*) n FROM transactions WHERE user_id=? AND match_status='unmatched'",
      userA,
    ) >= 5,
    'unmatched txns → suggestions/recommendations',
  );
  assert.ok(
    num(
      db,
      'SELECT COUNT(*) n FROM transactions WHERE user_id=? AND spending_category_id IS NOT NULL',
      userA,
    ) > 5,
    'categorised spending',
  );
  assert.ok(
    num(
      db,
      'SELECT COUNT(*) n FROM monthly_bill_state m JOIN bills b ON b.id=m.bill_id WHERE b.user_id=?',
      userA,
    ) >= 4,
    'skip/override/note/snooze',
  );
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM category_groups WHERE user_id=? AND is_seeded=1', userA),
    2,
  );
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM snowball_plans WHERE user_id=? AND is_seeded=1', userA),
    1,
  );
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM spending_budgets WHERE user_id=? AND is_seeded=1', userA),
    5,
  );
  assert.ok(
    num(
      db,
      'SELECT COUNT(*) n FROM spending_category_rules WHERE user_id=? AND is_seeded=1',
      userA,
    ) >= 3,
  );
  assert.ok(
    num(db, 'SELECT COUNT(*) n FROM monthly_income WHERE user_id=? AND is_seeded=1', userA) >= 1,
  );
  assert.ok(
    num(
      db,
      'SELECT COUNT(*) n FROM monthly_starting_amounts WHERE user_id=? AND is_seeded=1',
      userA,
    ) >= 1,
  );
  assert.ok(
    num(
      db,
      'SELECT COUNT(*) n FROM bill_merchant_rules r JOIN bills b ON b.id=r.bill_id WHERE b.user_id=?',
      userA,
    ) >= 1,
  );
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM data_sources WHERE user_id=? AND is_seeded=1', userA),
    1,
  );
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM financial_accounts WHERE user_id=? AND is_seeded=1', userA),
    1,
  );
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM calendar_tokens WHERE user_id=? AND is_seeded=1', userA),
    1,
  );
  assert.ok(
    db.prepare('SELECT snowball_extra_payment e FROM users WHERE id=?').get(userA).e > 0,
    'snowball extra-payment set',
  );

  // Matched payments link a real seeded transaction; money is integer cents.
  assert.ok(
    num(
      db,
      "SELECT COUNT(*) n FROM payments WHERE payment_source='transaction_match' AND transaction_id IN (SELECT id FROM transactions)",
    ) >= 3,
  );
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM payments WHERE amount <> CAST(amount AS INT)'),
    0,
    'cents-clean',
  );
});

test('seed refuses to double-seed (idempotency guard)', () => {
  assert.throws(
    () => seedDemoData(userA),
    (e) => e.code === 'ALREADY_SEEDED' && e.status === 409,
  );
});

test('clear removes ONLY seeded data — user bills/categories & no orphans', () => {
  // A user's OWN bill in a seed-created category (Healthcare) + one in a default category.
  const healthcareId = db
    .prepare("SELECT id FROM categories WHERE user_id=? AND name='Healthcare'")
    .get(userA).id;
  assert.equal(
    db.prepare('SELECT is_seeded s FROM categories WHERE id=?').get(healthcareId).s,
    1,
    'Healthcare is a seeded category',
  );
  const utilitiesId = db
    .prepare("SELECT id FROM categories WHERE user_id=? AND name='Utilities'")
    .get(userA).id;
  const ownBill1 = db
    .prepare(
      "INSERT INTO bills (user_id, name, category_id, due_day, expected_amount, active, is_seeded) VALUES (?, 'My Doctor', ?, 9, 5000, 1, 0)",
    )
    .run(userA, healthcareId).lastInsertRowid;
  const ownBill2 = db
    .prepare(
      "INSERT INTO bills (user_id, name, category_id, due_day, expected_amount, active, is_seeded) VALUES (?, 'My Power', ?, 9, 6000, 1, 0)",
    )
    .run(userA, utilitiesId).lastInsertRowid;

  const result = clearSeededDemoData(db, userA);
  assert.ok(result.removed > 0);

  // Every seeded surface gone.
  assert.equal(num(db, 'SELECT COUNT(*) n FROM bills WHERE user_id=? AND is_seeded=1', userA), 0);
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM transactions WHERE user_id=? AND is_seeded=1', userA),
    0,
  );
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM data_sources WHERE user_id=? AND is_seeded=1', userA),
    0,
  );
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM financial_accounts WHERE user_id=? AND is_seeded=1', userA),
    0,
  );
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM snowball_plans WHERE user_id=? AND is_seeded=1', userA),
    0,
  );
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM spending_budgets WHERE user_id=? AND is_seeded=1', userA),
    0,
  );
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM monthly_income WHERE user_id=? AND is_seeded=1', userA),
    0,
  );
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM category_groups WHERE user_id=? AND is_seeded=1', userA),
    0,
  );

  // No orphans: payments/transactions/state for deleted bills are gone.
  assert.equal(
    num(
      db,
      'SELECT COUNT(*) n FROM payments p LEFT JOIN bills b ON b.id=p.bill_id WHERE b.id IS NULL',
    ),
    0,
    'no orphan payments',
  );
  assert.equal(
    num(
      db,
      'SELECT COUNT(*) n FROM monthly_bill_state m LEFT JOIN bills b ON b.id=m.bill_id WHERE b.id IS NULL',
    ),
    0,
    'no orphan monthly_bill_state',
  );

  // User's own data preserved.
  assert.ok(db.prepare('SELECT 1 FROM bills WHERE id=?').get(ownBill1), 'user bill 1 survives');
  assert.ok(db.prepare('SELECT 1 FROM bills WHERE id=?').get(ownBill2), 'user bill 2 survives');
  assert.equal(
    db.prepare('SELECT category_id c FROM bills WHERE id=?').get(ownBill1).c,
    healthcareId,
    'still categorised (not SET NULL)',
  );
  // Healthcare seeded category SURVIVES because a user bill still references it.
  assert.ok(
    db.prepare('SELECT 1 FROM categories WHERE id=?').get(healthcareId),
    'referenced seeded category kept',
  );
  // A seeded spending category with no user bill IS removed.
  assert.equal(
    num(db, "SELECT COUNT(*) n FROM categories WHERE user_id=? AND name='Groceries'", userA),
    0,
    'unreferenced seeded category removed',
  );
  // Default categories untouched.
  assert.ok(
    db.prepare('SELECT 1 FROM categories WHERE id=?').get(utilitiesId),
    'default category kept',
  );
  // Snowball extra-payment reset.
  assert.equal(db.prepare('SELECT snowball_extra_payment e FROM users WHERE id=?').get(userA).e, 0);
});

test("clear leaves other users' seeded data untouched", () => {
  assert.equal(
    num(db, 'SELECT COUNT(*) n FROM bills WHERE user_id=? AND is_seeded=1', userB),
    23,
    'user B intact after clearing A',
  );
  assert.ok(
    num(db, 'SELECT COUNT(*) n FROM transactions WHERE user_id=? AND is_seeded=1', userB) > 0,
  );
});

test('clear is audited to import_history', () => {
  const row = db
    .prepare(
      "SELECT file_type, source_filename FROM import_history WHERE user_id=? AND file_type='clear-demo' ORDER BY id DESC LIMIT 1",
    )
    .get(userA);
  assert.equal(row.file_type, 'clear-demo');
  assert.equal(row.source_filename, 'clear-demo-data');
});

test('eraseUserData still wipes seeded demo data wholesale', () => {
  assert.ok(num(db, 'SELECT COUNT(*) n FROM bills WHERE user_id=?', userC) > 0);
  eraseUserData(db, userC);
  assert.equal(num(db, 'SELECT COUNT(*) n FROM bills WHERE user_id=?', userC), 0);
  assert.equal(num(db, 'SELECT COUNT(*) n FROM transactions WHERE user_id=?', userC), 0);
  assert.equal(num(db, 'SELECT COUNT(*) n FROM snowball_plans WHERE user_id=?', userC), 0);
  assert.equal(
    num(
      db,
      'SELECT COUNT(*) n FROM payments p LEFT JOIN bills b ON b.id=p.bill_id WHERE b.id IS NULL',
    ),
    0,
    'no orphans after erase',
  );
});
