'use strict';

// Batch 3: OFX/QFX import — parser handles SGML (OFX 1.x, unclosed leaf tags) and
// XML (OFX 2.x / QFX); commit inserts with the same dedupe scope as the CSV path.
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-ofx-${process.pid}.sqlite`);
process.env.DB_PATH = dbPath;

const { getDb, closeDb } = require('../db/database');
const { parseOfx, previewOfxTransactions, commitOfxTransactions } = require('../services/ofxImportService');

// OFX 1.x SGML — container tags closed, leaf tags unclosed.
const OFX_SGML = `OFXHEADER:100
DATA:OFXSGML
VERSION:102

<OFX>
<BANKMSGSRSV1><STMTTRNRS><STMTRS>
<CURDEF>USD
<BANKACCTFROM><ACCTID>000123456789</BANKACCTFROM>
<BANKTRANLIST>
<STMTTRN>
<TRNTYPE>DEBIT
<DTPOSTED>20260615120000
<TRNAMT>-42.50
<FITID>TXN-1001
<NAME>Netflix
<MEMO>Monthly subscription
</STMTTRN>
<STMTTRN>
<TRNTYPE>CREDIT
<DTPOSTED>20260616
<TRNAMT>1500.00
<FITID>TXN-1002
<NAME>Payroll
</STMTTRN>
</BANKTRANLIST>
</STMTRS></STMTTRNRS></BANKMSGSRSV1>
</OFX>`;

// OFX 2.x / QFX — XML (closed leaf tags) + Intuit tag we must ignore.
const QFX_XML = `<?xml version="1.0"?>
<OFX>
  <SONRS><INTU.BID>00123</INTU.BID></SONRS>
  <STMTTRN>
    <TRNTYPE>DEBIT</TRNTYPE>
    <DTPOSTED>20260701</DTPOSTED>
    <TRNAMT>-9.99</TRNAMT>
    <FITID>X-1</FITID>
    <NAME>Spotify &amp; Co</NAME>
  </STMTTRN>
</OFX>`;

let userId;
test.before(() => {
  const db = getDb();
  userId = db.prepare("INSERT INTO users (username, password_hash, role, active) VALUES ('ofx-user','x','user',1)").run().lastInsertRowid;
});
test.after(() => {
  closeDb();
  for (const s of ['', '-wal', '-shm']) { try { fs.unlinkSync(dbPath + s); } catch {} }
});

test('parseOfx reads SGML transactions (date, signed cents, fitid, name/memo)', () => {
  const txns = parseOfx(OFX_SGML);
  assert.equal(txns.length, 2);
  const netflix = txns[0];
  assert.equal(netflix.posted_date, '2026-06-15');
  assert.equal(netflix.amount, -4250, 'signed cents');
  assert.equal(netflix.provider_transaction_id, 'ofx:id:TXN-1001');
  assert.equal(netflix.payee, 'Netflix');
  assert.equal(netflix.memo, 'Monthly subscription');
  assert.equal(txns[1].amount, 150000, 'credit is positive cents');
});

test('parseOfx reads XML/QFX + decodes entities + ignores Intuit tags', () => {
  const txns = parseOfx(QFX_XML);
  assert.equal(txns.length, 1);
  assert.equal(txns[0].amount, -999);
  assert.equal(txns[0].posted_date, '2026-07-01');
  assert.equal(txns[0].payee, 'Spotify & Co');
});

test('parseOfx rejects non-OFX and empty files', () => {
  assert.throws(() => parseOfx('just some text'), /does not look like|OFX/i);
  assert.throws(() => parseOfx('<OFX></OFX>'), /No transactions/i);
});

test('preview → commit inserts, and re-commit dedupes', () => {
  const db = getDb();
  const before = db.prepare('SELECT COUNT(*) n FROM transactions WHERE user_id=?').get(userId).n;

  const p1 = previewOfxTransactions(userId, Buffer.from(OFX_SGML), { original_filename: 'stmt.ofx' });
  assert.equal(p1.count, 2);
  const r1 = commitOfxTransactions(userId, p1.import_session_id);
  assert.equal(r1.imported, 2);
  assert.equal(r1.skipped, 0);

  const mid = db.prepare('SELECT COUNT(*) n FROM transactions WHERE user_id=?').get(userId).n;
  assert.equal(mid - before, 2);

  // amounts persisted as integer cents
  const amt = db.prepare("SELECT amount FROM transactions WHERE provider_transaction_id='ofx:id:TXN-1001' AND user_id=?").get(userId).amount;
  assert.equal(amt, -4250);

  // re-import the same file → all skipped, no new rows
  const p2 = previewOfxTransactions(userId, Buffer.from(OFX_SGML), { original_filename: 'stmt.ofx' });
  const r2 = commitOfxTransactions(userId, p2.import_session_id);
  assert.equal(r2.imported, 0);
  assert.equal(r2.skipped, 2);
  const after = db.prepare('SELECT COUNT(*) n FROM transactions WHERE user_id=?').get(userId).n;
  assert.equal(after, mid, 'no duplicates on re-import');

  // logged to import_history
  const hist = db.prepare("SELECT file_type, rows_created, rows_skipped FROM import_history WHERE user_id=? ORDER BY id DESC LIMIT 1").get(userId);
  assert.equal(hist.file_type, 'ofx_transactions');
});
