'use strict';

// Snowball review #4/#6/#7: plan lifecycle endpoints — consolidated transitionPlan
// handler (state guards + standardized errors + ownership) and the batched,
// user-scoped enrichPlanWithProgress.
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-snowball-plan-${process.pid}.sqlite`);
process.env.DB_PATH = dbPath;

const { getDb, closeDb } = require('../db/database');
const router = require('../routes/snowball');

function handler(method, routePath) {
  const layer = router.stack.find(l => l.route?.path === routePath && l.route.methods[method]);
  assert.ok(layer, `${method.toUpperCase()} ${routePath} exists`);
  return layer.route.stack[layer.route.stack.length - 1].handle;
}
function call(method, routePath, { userId, params = {}, body = {} } = {}) {
  const h = handler(method, routePath);
  return new Promise((resolve) => {
    const req = { params, body, query: {}, user: { id: userId, role: 'user' } };
    const res = {
      statusCode: 200,
      status(c) { this.statusCode = c; return this; },
      json(d) { resolve({ status: this.statusCode, data: d }); },
    };
    h(req, res);
  });
}

let userA, userB;
test.before(() => {
  const db = getDb();
  userA = db.prepare("INSERT INTO users (username, password_hash, role, active) VALUES ('sb-a','x','user',1)").run().lastInsertRowid;
  userB = db.prepare("INSERT INTO users (username, password_hash, role, active) VALUES ('sb-b','x','user',1)").run().lastInsertRowid;
  const insBill = db.prepare(
    "INSERT INTO bills (user_id, name, due_day, expected_amount, current_balance, minimum_payment, interest_rate, snowball_include, active) VALUES (?, ?, 1, 0, ?, ?, 0, 1, 1)",
  );
  insBill.run(userA, 'Card A', 100000, 20000); // $1000 bal, $200 min
  insBill.run(userA, 'Card B', 300000, 20000); // $3000 bal, $200 min
});
test.after(() => {
  closeDb();
  for (const s of ['', '-wal', '-shm']) { try { fs.unlinkSync(dbPath + s); } catch {} }
});

test('start plan → enriched with user-scoped current_debts (dollars)', async () => {
  const { status, data } = await call('post', '/plans', { userId: userA, body: { method: 'snowball' } });
  assert.equal(status, 201);
  assert.equal(data.status, 'active');
  assert.ok(Array.isArray(data.current_debts) && data.current_debts.length === 2);
  const cardA = data.current_debts.find(d => d.name === 'Card A');
  assert.equal(cardA.current_balance, 1000, 'balance in dollars (fromCents)');
  assert.equal(cardA.starting_balance, 1000);
});

test('pause / resume / complete transitions + state guards + standardized errors', async () => {
  const plan = (await call('get', '/plans/active', { userId: userA })).data;
  const id = String(plan.id);

  assert.equal((await call('post', '/plans/:id/pause', { userId: userA, params: { id } })).data.status, 'paused');

  // pausing an already-paused plan → 400 with a standardized {message, code}
  const bad = await call('post', '/plans/:id/pause', { userId: userA, params: { id } });
  assert.equal(bad.status, 400);
  assert.equal(bad.data.code, 'INVALID_PLAN_STATE');
  assert.match(bad.data.message, /Only active plans can be paused/);

  assert.equal((await call('post', '/plans/:id/resume', { userId: userA, params: { id } })).data.status, 'active');
  assert.equal((await call('post', '/plans/:id/complete', { userId: userA, params: { id } })).data.status, 'completed');
});

test('another user cannot transition my plan (ownership)', async () => {
  const start = await call('post', '/plans', { userId: userA, body: {} });
  const id = String(start.data.id);
  const foreign = await call('post', '/plans/:id/abandon', { userId: userB, params: { id } });
  assert.equal(foreign.status, 404);
  assert.equal(foreign.data.code, 'NOT_FOUND');
  // still active for the real owner
  assert.equal((await call('get', '/plans/active', { userId: userA })).data.status, 'active');
});
