// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services).
import type { Req, Res, Next } from '../types/http';
('use strict');

const router = require('express').Router();
const { getDb } = require('../db/database.cts');
const { standardizeError } = require('../middleware/errorFormatter.cts');
const {
  decorateDataSource,
  ensureManualDataSource,
} = require('../services/transactionService.cts');
const {
  connectSimplefin,
  syncDataSource,
  backfillDataSource,
  disconnectDataSource,
} = require('../services/bankSyncService.cts');
const { sanitizeErrorMessage } = require('../services/simplefinService.cts');
const { getBankSyncConfig } = require('../services/bankSyncConfigService.cts');
const { syncLimiter } = require('../middleware/rateLimiter.cts');

const VALID_TYPES = new Set(['manual', 'file_import', 'provider_sync']);
const VALID_STATUSES = new Set(['active', 'inactive', 'error']);

function cleanFilter(value) {
  return typeof value === 'string' ? value.trim() : '';
}

function safeError(err, fallback) {
  const msg = sanitizeErrorMessage(err?.message || fallback);
  const status = typeof err?.status === 'number' ? err.status : 500;
  return { msg, status };
}

// ─── GET /api/data-sources/accounts/all ──────────────────────────────────────
// Returns all financial accounts for the user across all sources.
// Used by the bank tracking account picker.

router.get('/accounts/all', (req: Req, res: Res) => {
  try {
    const db = getDb();
    const accounts = db
      .prepare(
        `
      SELECT
        fa.id, fa.name, fa.org_name, fa.account_type,
        fa.balance, fa.available_balance, fa.currency,
        fa.monitored, ds.id AS source_id
      FROM financial_accounts fa
      JOIN data_sources ds ON ds.id = fa.data_source_id
      WHERE fa.user_id = ?
      ORDER BY fa.org_name COLLATE NOCASE ASC, fa.name COLLATE NOCASE ASC
    `,
      )
      .all(req.user.id);

    res.json(
      accounts.map((a) => ({
        ...a,
        monitored: a.monitored === 1,
        balance_dollars: a.balance !== null ? a.balance / 100 : null,
      })),
    );
  } catch (err) {
    res.status(500).json(standardizeError(err.message || 'Failed to load accounts', 'DB_ERROR'));
  }
});

// ─── GET /api/data-sources ────────────────────────────────────────────────────

router.get('/', (req: Req, res: Res) => {
  const db = getDb();
  ensureManualDataSource(db, req.user.id);

  const type = cleanFilter(req.query.type);
  const status = cleanFilter(req.query.status);

  if (type && !VALID_TYPES.has(type))
    return res
      .status(400)
      .json(
        standardizeError(
          'type must be manual, file_import, or provider_sync',
          'VALIDATION_ERROR',
          'type',
        ),
      );
  if (status && !VALID_STATUSES.has(status))
    return res
      .status(400)
      .json(
        standardizeError('status must be active, inactive, or error', 'VALIDATION_ERROR', 'status'),
      );

  let query = `
    SELECT
      ds.id, ds.user_id, ds.type, ds.provider, ds.name, ds.status,
      ds.config_json, ds.last_sync_at, ds.last_error, ds.created_at, ds.updated_at,
      COUNT(DISTINCT fa.id) AS account_count,
      COUNT(DISTINCT t.id)  AS transaction_count
    FROM data_sources ds
    LEFT JOIN financial_accounts fa ON fa.data_source_id = ds.id AND fa.user_id = ds.user_id
    LEFT JOIN transactions t        ON t.data_source_id  = ds.id AND t.user_id  = ds.user_id
    WHERE ds.user_id = ?
  `;
  const params = [req.user.id];

  if (type) {
    query += ' AND ds.type = ?';
    params.push(type);
  }
  if (status) {
    query += ' AND ds.status = ?';
    params.push(status);
  }

  query += `
    GROUP BY ds.id
    ORDER BY
      CASE WHEN ds.type = 'manual' THEN 0 ELSE 1 END,
      ds.name COLLATE NOCASE ASC
  `;

  res.json(
    db
      .prepare(query)
      .all(...params)
      .map(decorateDataSource),
  );
});

// ─── GET /api/data-sources/simplefin/status ──────────────────────────────────

router.get('/simplefin/status', (req: Req, res: Res) => {
  const { enabled, sync_days, seed_days } = getBankSyncConfig();
  const db = getDb();

  const hasConnections = !!db
    .prepare(
      "SELECT 1 FROM data_sources WHERE user_id = ? AND type = 'provider_sync' AND provider = 'simplefin' LIMIT 1",
    )
    .get(req.user.id);

  const hasMerchantRules = !!db
    .prepare('SELECT 1 FROM bill_merchant_rules WHERE user_id = ? LIMIT 1')
    .get(req.user.id);

  const timezone = process.env.TZ || Intl.DateTimeFormat().resolvedOptions().timeZone || null;
  res.json({
    enabled,
    sync_days,
    seed_days,
    has_connections: hasConnections,
    has_merchant_rules: hasMerchantRules,
    timezone,
  });
});

// ─── POST /api/data-sources/simplefin/connect ────────────────────────────────

router.post('/simplefin/connect', async (req: Req, res: Res) => {
  if (!getBankSyncConfig().enabled) {
    return res
      .status(503)
      .json(standardizeError('Bank sync is not enabled on this server', 'BANK_SYNC_DISABLED'));
  }

  const setupToken = typeof req.body?.setupToken === 'string' ? req.body.setupToken.trim() : '';
  if (!setupToken) {
    return res
      .status(400)
      .json(standardizeError('setupToken is required', 'VALIDATION_ERROR', 'setupToken'));
  }

  try {
    const db = getDb();
    const result = await connectSimplefin(db, req.user.id, setupToken);
    res.status(201).json(result);
  } catch (err) {
    const { msg, status } = safeError(err, 'Failed to connect SimpleFIN');
    res.status(status).json(standardizeError(msg, err?.code || 'SIMPLEFIN_ERROR'));
  }
});

// ─── GET /api/data-sources/:sourceId/accounts ────────────────────────────────

router.get('/:sourceId/accounts', (req: Req, res: Res) => {
  const sourceId = parseInt(req.params.sourceId, 10);
  if (!Number.isInteger(sourceId) || sourceId < 1) {
    return res
      .status(400)
      .json(standardizeError('Invalid data source id', 'VALIDATION_ERROR', 'sourceId'));
  }

  try {
    const db = getDb();

    const source = db
      .prepare('SELECT id FROM data_sources WHERE id = ? AND user_id = ?')
      .get(sourceId, req.user.id);
    if (!source)
      return res.status(404).json(standardizeError('Data source not found', 'NOT_FOUND'));

    const accounts = db
      .prepare(
        `
      SELECT
        fa.id, fa.provider_account_id, fa.name, fa.org_name, fa.account_type,
        fa.balance, fa.available_balance, fa.currency, fa.monitored,
        fa.created_at, fa.updated_at,
        COUNT(t.id) AS transaction_count
      FROM financial_accounts fa
      LEFT JOIN transactions t ON t.account_id = fa.id AND t.user_id = fa.user_id
      WHERE fa.data_source_id = ? AND fa.user_id = ?
      GROUP BY fa.id
      ORDER BY fa.name COLLATE NOCASE ASC
    `,
      )
      .all(sourceId, req.user.id);

    const txStmt = db.prepare(`
      SELECT t.id, t.posted_date, t.transacted_at, t.amount, t.currency,
             t.payee, t.description, t.memo, t.match_status, t.ignored,
             t.matched_bill_id, b.name AS matched_bill_name
      FROM transactions t
      LEFT JOIN bills b ON b.id = t.matched_bill_id AND b.user_id = t.user_id AND b.deleted_at IS NULL
      WHERE t.account_id = ? AND t.user_id = ?
      ORDER BY COALESCE(t.posted_date, substr(t.transacted_at, 1, 10), t.created_at) DESC, t.id DESC
      LIMIT 50
    `);

    const result = accounts.map((acc) => ({
      ...acc,
      monitored: acc.monitored === 1,
      transactions: acc.monitored === 1 ? txStmt.all(acc.id, req.user.id) : [],
    }));

    res.json(result);
  } catch (err) {
    res.status(500).json(standardizeError(err.message || 'Failed to load accounts', 'DB_ERROR'));
  }
});

// ─── PUT /api/data-sources/:sourceId/accounts/:accountId ─────────────────────

router.put('/:sourceId/accounts/:accountId', (req: Req, res: Res) => {
  const sourceId = parseInt(req.params.sourceId, 10);
  const accountId = parseInt(req.params.accountId, 10);
  if (
    !Number.isInteger(sourceId) ||
    sourceId < 1 ||
    !Number.isInteger(accountId) ||
    accountId < 1
  ) {
    return res.status(400).json(standardizeError('Invalid id', 'VALIDATION_ERROR'));
  }
  if (typeof req.body?.monitored !== 'boolean') {
    return res
      .status(400)
      .json(standardizeError('monitored must be a boolean', 'VALIDATION_ERROR', 'monitored'));
  }

  try {
    const db = getDb();
    const result = db
      .prepare(
        `
      UPDATE financial_accounts
      SET monitored = ?, updated_at = datetime('now')
      WHERE id = ? AND data_source_id = ? AND user_id = ?
    `,
      )
      .run(req.body.monitored ? 1 : 0, accountId, sourceId, req.user.id);

    if (result.changes === 0)
      return res.status(404).json(standardizeError('Account not found', 'NOT_FOUND'));

    const account = db
      .prepare('SELECT id, name, monitored FROM financial_accounts WHERE id = ?')
      .get(accountId);
    res.json({ ...account, monitored: account.monitored === 1 });
  } catch (err) {
    res.status(500).json(standardizeError(err.message || 'Failed to update account', 'DB_ERROR'));
  }
});

// ─── POST /api/data-sources/:id/sync ─────────────────────────────────────────

router.post('/:id/sync', syncLimiter, async (req: Req, res: Res) => {
  if (!getBankSyncConfig().enabled) {
    return res
      .status(503)
      .json(standardizeError('Bank sync is not enabled on this server', 'BANK_SYNC_DISABLED'));
  }

  const id = parseInt(req.params.id, 10);
  if (!Number.isInteger(id) || id < 1) {
    return res
      .status(400)
      .json(standardizeError('Invalid data source id', 'VALIDATION_ERROR', 'id'));
  }

  try {
    const db = getDb();
    const result = await syncDataSource(db, req.user.id, id);
    res.json(result);
  } catch (err) {
    const { msg, status } = safeError(err, 'Sync failed');
    res.status(status).json(standardizeError(msg, err?.code || 'SIMPLEFIN_ERROR'));
  }
});

// ─── POST /api/data-sources/sync-all ─────────────────────────────────────────
// Syncs every SimpleFIN source for the current user. Returns aggregated stats.

router.post('/sync-all', syncLimiter, async (req: Req, res: Res) => {
  if (!getBankSyncConfig().enabled) {
    return res
      .status(503)
      .json(standardizeError('Bank sync is not enabled on this server', 'BANK_SYNC_DISABLED'));
  }

  try {
    const db = getDb();
    const sources = db
      .prepare(
        "SELECT id FROM data_sources WHERE user_id = ? AND type = 'provider_sync' AND provider = 'simplefin'",
      )
      .all(req.user.id);

    if (sources.length === 0) {
      return res.status(404).json(standardizeError('No SimpleFIN connections found', 'NOT_FOUND'));
    }

    let accountsUpserted = 0;
    let transactionsNew = 0;
    let transactionsSkip = 0;
    let autoMatched = 0;
    const matchedBillSet = new Set();
    const lateAttrAll = [];
    const errors = [];

    for (const source of sources) {
      try {
        const result = await syncDataSource(db, req.user.id, source.id);
        accountsUpserted += result.accountsUpserted ?? 0;
        transactionsNew += result.transactionsNew ?? 0;
        transactionsSkip += result.transactionsSkip ?? 0;
        autoMatched += result.autoMatched ?? 0;
        for (const name of result.matched_bills ?? []) matchedBillSet.add(name);
        for (const attr of result.late_attributions ?? []) lateAttrAll.push(attr);
      } catch (err) {
        errors.push(sanitizeErrorMessage(err?.message || 'Sync failed'));
      }
    }

    res.json({
      accounts_upserted: accountsUpserted,
      transactions_new: transactionsNew,
      transactions_skip: transactionsSkip,
      auto_matched: autoMatched,
      matched_bills: [...matchedBillSet],
      late_attributions: lateAttrAll,
      errors,
    });
  } catch (err) {
    const { msg, status } = safeError(err, 'Sync failed');
    res.status(status).json(standardizeError(msg, 'SIMPLEFIN_ERROR'));
  }
});

// ─── POST /api/data-sources/:id/backfill ─────────────────────────────────────

router.post('/:id/backfill', syncLimiter, async (req: Req, res: Res) => {
  if (!getBankSyncConfig().enabled) {
    return res
      .status(503)
      .json(standardizeError('Bank sync is not enabled on this server', 'BANK_SYNC_DISABLED'));
  }

  const id = parseInt(req.params.id, 10);
  if (!Number.isInteger(id) || id < 1) {
    return res
      .status(400)
      .json(standardizeError('Invalid data source id', 'VALIDATION_ERROR', 'id'));
  }

  try {
    const db = getDb();
    const result = await backfillDataSource(db, req.user.id, id);
    res.json(result);
  } catch (err) {
    const { msg, status } = safeError(err, 'Backfill failed');
    res.status(status).json(standardizeError(msg, err?.code || 'SIMPLEFIN_ERROR'));
  }
});

// ─── DELETE /api/data-sources/:id ────────────────────────────────────────────

router.delete('/:id', (req: Req, res: Res) => {
  if (!getBankSyncConfig().enabled) {
    return res
      .status(503)
      .json(standardizeError('Bank sync is not enabled on this server', 'BANK_SYNC_DISABLED'));
  }

  const id = parseInt(req.params.id, 10);
  if (!Number.isInteger(id) || id < 1) {
    return res
      .status(400)
      .json(standardizeError('Invalid data source id', 'VALIDATION_ERROR', 'id'));
  }

  try {
    disconnectDataSource(getDb(), req.user.id, id);
    res.json({ ok: true });
  } catch (err) {
    const { msg, status } = safeError(err, 'Failed to disconnect');
    res.status(status).json(standardizeError(msg, err?.code || 'DISCONNECT_ERROR'));
  }
});

module.exports = router;
