'use strict';

import type { Db } from '../types/db';

const { getDb } = require('../db/database.cts');
const { getBankSyncConfig } = require('./bankSyncConfigService.cts');
const { syncDataSource } = require('./bankSyncService.cts');

// Skip a source if it was synced less than this long ago (catches recent manual syncs)
const MIN_SYNC_AGE_MS = 60 * 60 * 1000; // 1 hour
// Pause between each source to avoid hammering SimpleFIN
const STAGGER_DELAY_MS = 3000;

let timer: any = null;
let running = false;
let lastRunAt: string | null = null;
let nextRunAt: string | null = null;

function intervalMs(): number {
  const { sync_interval_hours } = getBankSyncConfig();
  return Math.round(sync_interval_hours * 3600000);
}

function needsSync(source: any): boolean {
  if (!source.last_sync_at) return true;
  const age = Date.now() - new Date(source.last_sync_at).getTime();
  return age >= MIN_SYNC_AGE_MS;
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function runCycle(): Promise<void> {
  if (running) return;

  const config = getBankSyncConfig();
  if (!config.enabled) {
    console.log('[bankSync] Disabled — skipping cycle');
    return;
  }

  const debug = config.debug_logging;

  let db: Db, sources: any[];
  try {
    db = getDb();
    sources = db
      .prepare(
        `
      SELECT * FROM data_sources
      WHERE type = 'provider_sync' AND provider = 'simplefin'
      ORDER BY last_sync_at ASC
    `,
      )
      .all();
  } catch (err: any) {
    console.error('[bankSync] Worker failed to load sources:', err.message);
    return;
  }

  if (sources.length === 0) {
    if (debug) console.log('[bankSync] No SimpleFIN sources configured — skipping cycle');
    return;
  }

  console.log(`[bankSync] Cycle starting — ${sources.length} source(s)`);
  running = true;
  lastRunAt = new Date().toISOString();

  let synced = 0;
  let skipped = 0;
  let failed = 0;

  for (let i = 0; i < sources.length; i++) {
    const source = sources[i];

    if (!needsSync(source)) {
      if (debug)
        console.log(
          `[bankSync] Source #${source.id} (user ${source.user_id}): recently synced — skipping`,
        );
      skipped++;
      continue;
    }

    if (debug)
      console.log(`[bankSync] Source #${source.id} (user ${source.user_id}): starting sync`);
    try {
      const result = await syncDataSource(db, source.user_id, source.id, { debug });
      synced++;
      const extra = [
        result.transactionsPosted ? `${result.transactionsPosted} settled` : null,
        result.pendingCleared ? `${result.pendingCleared} pending cleared` : null,
      ]
        .filter(Boolean)
        .join(', ');
      console.log(
        `[bankSync] Source #${source.id}: OK — ${result.accountsUpserted} account(s), ${result.transactionsNew} new, ${result.transactionsSkip} skipped${extra ? `, ${extra}` : ''}${result.errlist ? ` [partial: ${result.errlist}]` : ''}`,
      );
    } catch (err: any) {
      failed++;
      console.error(`[bankSync] Source #${source.id}: FAILED — ${err.message}`);
    }

    // Stagger requests — don't fire them all simultaneously
    if (i < sources.length - 1) await sleep(STAGGER_DELAY_MS);
  }

  console.log(`[bankSync] Cycle complete — ${synced} synced, ${failed} failed, ${skipped} skipped`);
  running = false;
}

function scheduleNext(): void {
  const ms = intervalMs();
  nextRunAt = new Date(Date.now() + ms).toISOString();

  timer = setTimeout(() => {
    runCycle()
      .catch((err) => {
        console.error('[bankSync] Worker cycle error:', err.message);
        running = false;
      })
      .finally(scheduleNext);
  }, ms);

  // Don't hold the event loop open if the server is shutting down
  if (timer.unref) timer.unref();
}

function start(): void {
  if (timer) return;
  scheduleNext();
  console.log(
    `[bankSync] Auto-sync worker started (interval: ${getBankSyncConfig().sync_interval_hours}h)`,
  );
}

function stop(): void {
  clearTimeout(timer);
  timer = null;
}

function getStatus() {
  return {
    running,
    interval_hours: getBankSyncConfig().sync_interval_hours,
    last_run_at: lastRunAt,
    next_run_at: nextRunAt,
  };
}

module.exports = { start, stop, getStatus };
