// @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';
const router = require('express').Router();
const { standardizeError } = require('../middleware/errorFormatter.cts');
const { getDb } = require('../db/database.cts');
const {
  applyBankPaymentAsSourceOfTruth,
  reactivatePaymentsOverriddenBy,
} = require('../services/paymentAccountingService.cts');
const {
  listMatchSuggestions,
  rejectMatchSuggestion,
} = require('../services/matchSuggestionService.cts');
const { learnMerchantRuleFromMatch } = require('../services/billMerchantRuleService.cts');
const { markMatched, markUnmatched } = require('../services/transactionMatchState.cts');
const { serializePayment } = require('../services/paymentValidation.cts');
const { todayLocal } = require('../utils/dates.mts');

function sendMatchError(res, err, fallbackMessage = 'Match operation failed') {
  if (err.status) {
    return res
      .status(err.status)
      .json(standardizeError(err.message, err.code || 'MATCH_ERROR', err.field));
  }
  console.error('[matches] service error:', err.stack || err.message);
  return res.status(500).json(standardizeError(fallbackMessage, 'MATCH_ERROR'));
}

// GET /api/matches/suggestions
router.get('/suggestions', (req: Req, res: Res) => {
  try {
    res.json(listMatchSuggestions(req.user.id, req.query));
  } catch (err) {
    return sendMatchError(res, err, 'Match suggestions failed');
  }
});

// POST /api/matches/:id/reject
router.post('/:id/reject', (req: Req, res: Res) => {
  try {
    res.json(rejectMatchSuggestion(req.user.id, req.params.id));
  } catch (err) {
    return sendMatchError(res, err, 'Rejecting match suggestion failed');
  }
});

// POST /api/matches/confirm — link a transaction to a bill and record a payment
router.post('/confirm', (req: Req, res: Res) => {
  const txId = parseInt(req.body?.transaction_id, 10);
  const billId = parseInt(req.body?.bill_id, 10);
  if (!Number.isInteger(txId) || !Number.isInteger(billId)) {
    return res
      .status(400)
      .json(
        standardizeError('transaction_id and bill_id are required integers', 'VALIDATION_ERROR'),
      );
  }

  const db = getDb();
  const tx = db
    .prepare('SELECT * FROM transactions WHERE id = ? AND user_id = ?')
    .get(txId, req.user.id);
  if (!tx)
    return res
      .status(404)
      .json(standardizeError('Transaction not found', 'NOT_FOUND', 'transaction_id'));
  if (tx.match_status === 'matched') {
    return res
      .status(409)
      .json(
        standardizeError(
          'Transaction is already matched to a bill',
          'ALREADY_MATCHED',
          'transaction_id',
        ),
      );
  }

  const bill = db
    .prepare('SELECT * FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL')
    .get(billId, req.user.id);
  if (!bill)
    return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));

  const existing = db
    .prepare('SELECT id FROM payments WHERE transaction_id = ? AND deleted_at IS NULL')
    .get(txId);
  if (existing)
    return res
      .status(409)
      .json(standardizeError('A payment is already linked to this transaction', 'DUPLICATE_MATCH'));

  const paidDate =
    tx.posted_date || (tx.transacted_at ? tx.transacted_at.slice(0, 10) : todayLocal());
  const amount = Math.round(Math.abs(tx.amount)); // tx.amount and payments.amount are both cents

  try {
    db.exec('BEGIN');
    const payResult = db
      .prepare(
        "INSERT INTO payments (bill_id, amount, paid_date, payment_source, transaction_id) VALUES (?, ?, ?, 'transaction_match', ?)",
      )
      .run(billId, amount, paidDate, txId);

    const paymentForAccounting = db
      .prepare('SELECT * FROM payments WHERE id = ?')
      .get(payResult.lastInsertRowid);
    applyBankPaymentAsSourceOfTruth(db, bill, paymentForAccounting);

    markMatched(db, req.user.id, txId, billId);

    // Learn a merchant→bill rule from this explicit confirmation so future
    // synced transactions from the same merchant auto-match. Best-effort.
    learnMerchantRuleFromMatch(db, req.user.id, billId, tx);

    db.exec('COMMIT');

    const payment = db
      .prepare('SELECT * FROM payments WHERE id = ?')
      .get(payResult.lastInsertRowid);
    const updated = db
      .prepare(
        `
      SELECT t.*, b.name AS matched_bill_name
      FROM transactions t
      LEFT JOIN bills b ON b.id = t.matched_bill_id AND b.deleted_at IS NULL
      WHERE t.id = ?
    `,
      )
      .get(txId);
    res.json({ transaction: updated, payment: serializePayment(payment) });
  } catch (err) {
    try {
      db.exec('ROLLBACK');
    } catch {}
    return sendMatchError(res, err, 'Failed to confirm match');
  }
});

// POST /api/matches/:transactionId/unmatch — remove a manual match
router.post('/:transactionId/unmatch', (req: Req, res: Res) => {
  const txId = parseInt(req.params.transactionId, 10);
  if (!Number.isInteger(txId)) {
    return res
      .status(400)
      .json(standardizeError('transactionId must be an integer', 'VALIDATION_ERROR'));
  }

  const db = getDb();
  const tx = db
    .prepare('SELECT * FROM transactions WHERE id = ? AND user_id = ?')
    .get(txId, req.user.id);
  if (!tx) return res.status(404).json(standardizeError('Transaction not found', 'NOT_FOUND'));
  if (tx.match_status !== 'matched') {
    return res.status(409).json(standardizeError('Transaction is not matched', 'NOT_MATCHED'));
  }

  try {
    db.exec('BEGIN');
    const matchedPayments = db
      .prepare(
        `
      SELECT *
      FROM payments
      WHERE transaction_id = ? AND payment_source = 'transaction_match' AND deleted_at IS NULL
    `,
      )
      .all(txId);
    for (const payment of matchedPayments) {
      reactivatePaymentsOverriddenBy(db, payment.id);
    }
    db.prepare(
      `
      UPDATE payments SET deleted_at = datetime('now'), updated_at = datetime('now')
      WHERE transaction_id = ? AND payment_source = 'transaction_match' AND deleted_at IS NULL
    `,
    ).run(txId);
    markUnmatched(db, req.user.id, txId);
    db.exec('COMMIT');
    res.json({ ok: true });
  } catch (err) {
    try {
      db.exec('ROLLBACK');
    } catch {}
    return sendMatchError(res, err, 'Failed to unmatch transaction');
  }
});

module.exports = router;
