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-bank-sync-test-${process.pid}.sqlite`);
process.env.DB_PATH = dbPath;

const { getDb, closeDb } = require('../db/database');
const { encryptSecret } = require('../services/encryptionService');
const { syncDataSource } = require('../services/bankSyncService');

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(`bank-sync-${suffix}`, `bank-sync-${suffix}@local`).lastInsertRowid;
}

function createSource(db, userId) {
  return db.prepare(`
    INSERT INTO data_sources (user_id, type, provider, name, status, encrypted_secret)
    VALUES (?, 'provider_sync', 'simplefin', 'SimpleFIN', 'active', ?)
  `).run(userId, encryptSecret('https://user:pass@example.com/simplefin')).lastInsertRowid;
}

function createAccount(db, userId, dataSourceId, providerAccountId, monitored) {
  return db.prepare(`
    INSERT INTO financial_accounts
      (user_id, data_source_id, provider_account_id, name, currency, monitored)
    VALUES (?, ?, ?, ?, 'USD', ?)
  `).run(userId, dataSourceId, providerAccountId, providerAccountId, monitored ? 1 : 0).lastInsertRowid;
}

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

test('SimpleFIN sync skips storing transactions for unmonitored accounts', async () => {
  const db = getDb();
  const userId = createUser(db, 'skip-unmonitored');
  const dataSourceId = createSource(db, userId);
  const mutedAccountId = createAccount(db, userId, dataSourceId, 'muted-account', false);

  const originalFetch = global.fetch;
  global.fetch = async () => ({
    ok: true,
    status: 200,
    json: async () => ({
      accounts: [
        {
          id: 'muted-account',
          name: 'Muted Account',
          currency: 'USD',
          balance: '100.00',
          transactions: [
            { id: 'muted-tx-1', amount: '-12.34', posted: 1772323200, description: 'Muted charge' },
          ],
        },
        {
          id: 'tracked-account',
          name: 'Tracked Account',
          currency: 'USD',
          balance: '200.00',
          transactions: [
            { id: 'tracked-tx-1', amount: '-56.78', posted: 1772323200, description: 'Tracked charge' },
          ],
        },
      ],
    }),
  });

  try {
    const result = await syncDataSource(db, userId, dataSourceId);
    assert.equal(result.transactionsNew, 1);
  } finally {
    global.fetch = originalFetch;
  }

  const mutedTransactions = db.prepare('SELECT COUNT(*) AS count FROM transactions WHERE account_id = ?').get(mutedAccountId).count;
  assert.equal(mutedTransactions, 0);

  const trackedAccount = db.prepare(`
    SELECT id, monitored
    FROM financial_accounts
    WHERE user_id = ? AND data_source_id = ? AND provider_account_id = 'tracked-account'
  `).get(userId, dataSourceId);
  assert.equal(trackedAccount.monitored, 1);

  const trackedTransactions = db.prepare('SELECT COUNT(*) AS count FROM transactions WHERE account_id = ?').get(trackedAccount.id).count;
  assert.equal(trackedTransactions, 1);
});

test('SimpleFIN pending transactions: insert, settle in place, then prune orphans', async () => {
  const db = getDb();
  const userId = createUser(db, 'pending-lifecycle');
  const dataSourceId = createSource(db, userId);
  const accountId = createAccount(db, userId, dataSourceId, 'pending-acct', true);

  const originalFetch = global.fetch;
  const mockAccounts = (transactions) => {
    global.fetch = async () => ({
      ok: true, status: 200,
      json: async () => ({
        accounts: [{ id: 'pending-acct', name: 'Pending Acct', currency: 'USD', balance: '500.00', transactions }],
      }),
    });
  };

  try {
    // 1. First sync — one pending charge (posted=0, pending:true)
    mockAccounts([
      { id: 'tx-1', amount: '-42.00', posted: 0, pending: true, description: 'Coffee (pending)' },
    ]);
    let result = await syncDataSource(db, userId, dataSourceId);
    assert.equal(result.transactionsNew, 1);

    let row = db.prepare('SELECT pending, posted_date FROM transactions WHERE provider_transaction_id = ?')
      .get('simplefin:pending-acct:tx-1');
    assert.equal(row.pending, 1, 'stored as pending');
    assert.equal(row.posted_date, null, 'pending has no posted_date');

    // 2. Second sync — same id now settled (posted timestamp, pending gone)
    mockAccounts([
      { id: 'tx-1', amount: '-42.50', posted: 1772323200, description: 'Coffee' },
    ]);
    result = await syncDataSource(db, userId, dataSourceId);
    assert.equal(result.transactionsNew, 0, 'no new row — updated in place');
    assert.equal(result.transactionsPosted, 1, 'counted as settled');

    const rows = db.prepare('SELECT pending, posted_date, amount FROM transactions WHERE account_id = ?').all(accountId);
    assert.equal(rows.length, 1, 'no duplicate row created on settle');
    assert.equal(rows[0].pending, 0, 'flipped to posted');
    assert.equal(rows[0].posted_date, '2026-03-01', 'gained posted_date');
    assert.equal(rows[0].amount, -4250, 'amount refreshed to settled value');

    // 3. Add a fresh pending charge, then a sync where it vanishes (re-posted under a new id)
    mockAccounts([
      { id: 'tx-2', amount: '-9.99', posted: 0, pending: true, description: 'Snack (pending)' },
    ]);
    await syncDataSource(db, userId, dataSourceId);
    assert.equal(
      db.prepare("SELECT COUNT(*) AS n FROM transactions WHERE account_id = ? AND pending = 1").get(accountId).n,
      1, 'pending tx-2 stored',
    );

    // tx-2 disappears from the feed (settled under a new id); orphan prune should clear it
    mockAccounts([
      { id: 'tx-3', amount: '-9.99', posted: 1772323200, description: 'Snack' },
    ]);
    result = await syncDataSource(db, userId, dataSourceId);
    assert.equal(result.pendingCleared, 1, 'orphaned pending row pruned');
    assert.equal(
      db.prepare("SELECT COUNT(*) AS n FROM transactions WHERE account_id = ? AND pending = 1").get(accountId).n,
      0, 'no stale pending rows remain',
    );
  } finally {
    global.fetch = originalFetch;
  }
});
