'use strict';

// Phase 1 — paymentValidation gates EVERY payment write (manual/quick/bulk/
// import). It normalizes dollars → integer cents and rejects bad input before it
// can touch a balance. Pure functions, no DB. Pins: required-field errors + the
// field tag, dollars→cents conversion, amount/date validity, payment_source
// whitelist, and the require* option matrix.
const test = require('node:test');
const assert = require('node:assert/strict');
const {
  validatePaymentInput, validatePositiveAmount, validateIsoDate, validatePaymentSource, PAYMENT_SOURCES,
} = require('../services/paymentValidation.cts');

test('happy path: normalizes dollars → integer cents and echoes the fields', () => {
  const r = validatePaymentInput({ bill_id: '5', amount: 100, paid_date: '2026-07-01', payment_source: 'manual' });
  assert.equal(r.error, undefined);
  assert.deepEqual(r.normalized, { bill_id: 5, amount: 10000, paid_date: '2026-07-01', payment_source: 'manual' });
  assert.equal(validatePaymentInput({ bill_id: 1, amount: '12.50', paid_date: '2026-01-31' }).normalized.amount, 1250, 'string dollars convert too');
});

test('required fields: each missing field errors with its field tag', () => {
  assert.deepEqual(pick(validatePaymentInput({ amount: 1, paid_date: '2026-07-01' })), { error: 'bill_id is required', field: 'bill_id' });
  assert.deepEqual(pick(validatePaymentInput({ bill_id: 1, paid_date: '2026-07-01' })), { error: 'amount is required', field: 'amount' });
  assert.deepEqual(pick(validatePaymentInput({ bill_id: 1, amount: 1 })), { error: 'paid_date is required', field: 'paid_date' });
});

test('bill_id must be a positive integer', () => {
  assert.match(validatePaymentInput({ bill_id: 'abc', amount: 1, paid_date: '2026-07-01' }).error, /positive integer/);
  assert.match(validatePaymentInput({ bill_id: '-5', amount: 1, paid_date: '2026-07-01' }).error, /positive integer/);
});

test('amount must be a positive number (rejects 0, negative, non-numeric)', () => {
  for (const bad of [0, -5, 'abc', NaN]) {
    assert.match(validatePositiveAmount(bad).error, /positive number/, `rejects ${String(bad)}`);
  }
  assert.equal(validatePositiveAmount(100).value, 10000);
  // NB: the validator enforces a positive lower bound but NO upper bound — a very
  // large amount is accepted (64-bit INTEGER column). Pinned as intended behaviour.
  assert.equal(validatePositiveAmount(100000000).value, 10000000000);
});

test('paid_date must be a real YYYY-MM-DD calendar date', () => {
  assert.equal(validateIsoDate('2026-07-01').value, '2026-07-01');
  assert.match(validateIsoDate('2026-13-01').error, /real calendar date/, 'month 13 rejected (format-valid, calendar-invalid)');
  assert.match(validateIsoDate('2026-02-30').error, /real calendar date/, 'Feb 30 rejected');
  assert.match(validateIsoDate('07/01/2026').error, /valid date/, 'wrong format rejected');
  assert.match(validateIsoDate(20260701).error, /valid date/, 'non-string rejected');
});

test('payment_source is whitelisted, and only validated when present', () => {
  for (const s of PAYMENT_SOURCES) assert.equal(validatePaymentSource(s).value, s);
  assert.match(validatePaymentSource('hacker').error, /must be one of/);
  // absent → no error, not in normalized
  assert.equal(validatePaymentInput({ bill_id: 1, amount: 1, paid_date: '2026-07-01' }).normalized.payment_source, undefined);
  assert.equal(validatePaymentInput({ bill_id: 1, amount: 1, paid_date: '2026-07-01', payment_source: 'nope' }).error !== undefined, true);
});

test('option matrix: require*=false skips an absent field but still validates a present one', () => {
  const opts = { requireBillId: false, requireAmount: false, requirePaidDate: false };
  assert.deepEqual(validatePaymentInput({ amount: 5 }, opts).normalized, { amount: 500 }, 'only the present field is normalized');
  assert.equal(validatePaymentInput({}, opts).error, undefined, 'all-absent is valid when nothing is required');
  // present-but-invalid is still caught even when not required
  assert.match(validatePaymentInput({ bill_id: 'x' }, opts).error, /positive integer/);
});

test('fieldPrefix namespaces the field tag (for bulk row errors)', () => {
  const r = validatePaymentInput({ amount: 1, paid_date: '2026-07-01' }, { fieldPrefix: 'items.0.' });
  assert.equal(r.field, 'items.0.bill_id');
});

function pick(r) { return { error: r.error, field: r.field }; }
