// @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 express = require('express');
const { standardizeError } = require('../middleware/errorFormatter.cts');
const router = express.Router();
const os = require('os');
const path = require('path');
const fs = require('fs');
const Database = require('better-sqlite3');
const xlsx = require('xlsx');
const { getDb } = require('../db/database.cts');
const { fromCents } = require('../utils/money.mts');

// GET /api/export?year=2026&format=csv   — or a date range: ?from=YYYY-MM-DD&to=YYYY-MM-DD
router.get('/', (req: Req, res: Res) => {
  const db = getDb();
  const format = (req.query.format || 'csv').toLowerCase();
  const { from, to } = req.query;
  const isDate = (s) => typeof s === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(s);

  // Filter by an explicit date range (both bounds required) or by a single year.
  let where, whereParams, label;
  if (from != null || to != null) {
    if (!isDate(from) || !isDate(to)) {
      return res
        .status(400)
        .json(
          standardizeError('from and to must both be YYYY-MM-DD dates', 'VALIDATION_ERROR', 'from'),
        );
    }
    if (from > to) {
      return res
        .status(400)
        .json(standardizeError('from must be on or before to', 'VALIDATION_ERROR', 'from'));
    }
    where = 'p.paid_date BETWEEN ? AND ?';
    whereParams = [from, to];
    label = `${from}_to_${to}`;
  } else {
    const year = parseInt(req.query.year || new Date().getFullYear(), 10);
    if (isNaN(year) || year < 2000 || year > 2100) {
      return res
        .status(400)
        .json(
          standardizeError(
            'year must be a 4-digit integer between 2000 and 2100',
            'VALIDATION_ERROR',
            'year',
          ),
        );
    }
    where = "strftime('%Y', p.paid_date) = ?";
    whereParams = [String(year)];
    label = String(year);
  }

  const rows = db
    .prepare(
      `
    SELECT
      p.paid_date,
      p.bill_id,
      b.name        AS bill_name,
      c.name        AS category,
      b.expected_amount,
      p.amount      AS paid_amount,
      p.method,
      p.notes,
      p.created_at
    FROM payments p
    JOIN bills b ON b.id = p.bill_id
    LEFT JOIN categories c ON c.id = b.category_id AND c.deleted_at IS NULL
    WHERE ${where}
      AND b.user_id = ?
      AND b.deleted_at IS NULL
      AND p.deleted_at IS NULL
    ORDER BY p.paid_date ASC, b.name ASC
  `,
    )
    .all(...whereParams, req.user.id);

  const mbsStmt = db.prepare(
    'SELECT actual_amount, notes FROM monthly_bill_state WHERE bill_id=? AND year=? AND month=?',
  );

  if (format === 'csv') {
    const header = 'Date,Bill,Category,Expected,Paid,Method,Notes,Actual Amount,Monthly Notes\n';
    const escCsv = (v) => {
      if (v == null) return '';
      const s = String(v);
      return /[,"\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
    };

    const body = rows
      .map((r) => {
        const paidMonth = parseInt(r.paid_date.slice(5, 7), 10);
        const paidYear = parseInt(r.paid_date.slice(0, 4), 10);
        const mbs = mbsStmt.get(r.bill_id, paidYear, paidMonth);
        return [
          r.paid_date,
          escCsv(r.bill_name),
          escCsv(r.category),
          fromCents(r.expected_amount).toFixed(2),
          fromCents(r.paid_amount).toFixed(2),
          escCsv(r.method),
          escCsv(r.notes),
          mbs?.actual_amount != null ? fromCents(mbs.actual_amount).toFixed(2) : '',
          escCsv(mbs?.notes ?? null),
        ].join(',');
      })
      .join('\n');

    res.setHeader('Content-Type', 'text/csv');
    res.setHeader('Content-Disposition', `attachment; filename="bills-${label}.csv"`);
    return res.send(header + body);
  }

  // Fallback: JSON — enrich each row with monthly_bill_state overrides
  const enriched = rows.map((r) => {
    const paidMonth = parseInt(r.paid_date.slice(5, 7), 10);
    const paidYear = parseInt(r.paid_date.slice(0, 4), 10);
    const mbs = mbsStmt.get(r.bill_id, paidYear, paidMonth);
    return {
      ...r,
      expected_amount: fromCents(r.expected_amount),
      paid_amount: fromCents(r.paid_amount),
      actual_amount: fromCents(mbs?.actual_amount ?? null),
      monthly_notes: mbs?.notes ?? null,
    };
  });
  res.json({ range: label, count: enriched.length, payments: enriched });
});

function getUserExportData(userId) {
  const db = getDb();
  const categories = db
    .prepare(
      'SELECT id, name, created_at, updated_at FROM categories WHERE user_id = ? AND deleted_at IS NULL ORDER BY name',
    )
    .all(userId);
  const bills = db
    .prepare(
      `
    SELECT id, name, category_id, due_day, override_due_date, bucket, expected_amount, interest_rate,
           billing_cycle, cycle_type, cycle_day, autopay_enabled, autodraft_status, website, username,
           account_info, has_2fa, active, notes, created_at, updated_at
    FROM bills
    WHERE user_id = ? AND deleted_at IS NULL
    ORDER BY active DESC, due_day ASC, name ASC
  `,
    )
    .all(userId)
    .map((b) => ({ ...b, expected_amount: fromCents(b.expected_amount) }));
  const payments = db
    .prepare(
      `
    SELECT p.id, p.bill_id, p.amount, p.paid_date, p.method, p.notes,
           CASE WHEN p.payment_source = 'transaction_match' THEN 'manual' ELSE p.payment_source END AS payment_source,
           NULL AS transaction_id, p.created_at, p.updated_at
    FROM payments p
    JOIN bills b ON b.id = p.bill_id
    WHERE b.user_id = ? AND b.deleted_at IS NULL AND p.deleted_at IS NULL
    ORDER BY p.paid_date ASC, p.id ASC
  `,
    )
    .all(userId)
    .map((p) => ({ ...p, amount: fromCents(p.amount) }));
  const monthlyState = db
    .prepare(
      `
    SELECT m.id, m.bill_id, m.year, m.month, m.actual_amount, m.notes, m.is_skipped, m.created_at, m.updated_at
    FROM monthly_bill_state m
    JOIN bills b ON b.id = m.bill_id
    WHERE b.user_id = ? AND b.deleted_at IS NULL
    ORDER BY m.year, m.month, m.bill_id
  `,
    )
    .all(userId)
    .map((m) => ({ ...m, actual_amount: fromCents(m.actual_amount) }));
  const monthlyStartingAmounts = db
    .prepare(
      `
    SELECT id, year, month, first_amount, fifteenth_amount, other_amount, notes, created_at, updated_at
    FROM monthly_starting_amounts
    WHERE user_id = ?
    ORDER BY year, month
  `,
    )
    .all(userId)
    .map((r) => ({
      ...r,
      first_amount: fromCents(r.first_amount),
      fifteenth_amount: fromCents(r.fifteenth_amount),
      other_amount: fromCents(r.other_amount),
    }));
  const historyRanges = db
    .prepare(
      `
    SELECT id, bill_id, start_year, start_month, end_year, end_month, label, created_at, updated_at
    FROM bill_history_ranges
    WHERE bill_id IN (SELECT id FROM bills WHERE user_id = ? AND deleted_at IS NULL)
    ORDER BY bill_id, start_year, start_month
  `,
    )
    .all(userId);
  const notes = [
    ...bills.filter((b) => b.notes).map((b) => ({ type: 'bill', bill_id: b.id, notes: b.notes })),
    ...payments
      .filter((p) => p.notes)
      .map((p) => ({ type: 'payment', payment_id: p.id, bill_id: p.bill_id, notes: p.notes })),
    ...monthlyState
      .filter((m) => m.notes)
      .map((m) => ({
        type: 'monthly_state',
        monthly_state_id: m.id,
        bill_id: m.bill_id,
        year: m.year,
        month: m.month,
        notes: m.notes,
      })),
  ];
  const metadata = {
    exported_at: new Date().toISOString(),
    export_type: 'user_data',
    includes: [
      'Bills',
      'Payments',
      'Categories',
      'Monthly bill state',
      'Monthly starting amounts',
      'Bill history ranges',
      'Notes',
      'Export metadata',
    ],
    counts: {
      bills: bills.length,
      payments: payments.length,
      categories: categories.length,
      monthly_bill_state: monthlyState.length,
      monthly_starting_amounts: monthlyStartingAmounts.length,
      bill_history_ranges: historyRanges.length,
      notes: notes.length,
    },
  };
  return {
    metadata,
    categories,
    bills,
    payments,
    monthly_bill_state: monthlyState,
    monthly_starting_amounts: monthlyStartingAmounts,
    bill_history_ranges: historyRanges,
    notes,
  };
}

router.get('/user-excel', (req: Req, res: Res) => {
  const data = getUserExportData(req.user.id);
  const wb = xlsx.utils.book_new();
  xlsx.utils.book_append_sheet(wb, xlsx.utils.json_to_sheet([data.metadata]), 'Export Metadata');
  xlsx.utils.book_append_sheet(wb, xlsx.utils.json_to_sheet(data.bills), 'Bills');
  xlsx.utils.book_append_sheet(wb, xlsx.utils.json_to_sheet(data.payments), 'Payments');
  xlsx.utils.book_append_sheet(wb, xlsx.utils.json_to_sheet(data.categories), 'Categories');
  xlsx.utils.book_append_sheet(
    wb,
    xlsx.utils.json_to_sheet(data.monthly_bill_state),
    'Monthly State',
  );
  xlsx.utils.book_append_sheet(
    wb,
    xlsx.utils.json_to_sheet(data.monthly_starting_amounts),
    'Monthly Starting Amounts',
  );
  xlsx.utils.book_append_sheet(
    wb,
    xlsx.utils.json_to_sheet(data.bill_history_ranges),
    'History Ranges',
  );
  xlsx.utils.book_append_sheet(wb, xlsx.utils.json_to_sheet(data.notes), 'Notes');
  const buffer = xlsx.write(wb, { type: 'buffer', bookType: 'xlsx' });
  res.setHeader(
    'Content-Type',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  );
  res.setHeader('Content-Disposition', 'attachment; filename="bill-tracker-user-export.xlsx"');
  res.send(buffer);
});

// Build the SQLite user-DB export to a temp file and return its path. Extracted
// so the export→import round-trip can be regression-tested (exportImportRoundTrip).
function buildUserDbExportFile(userId) {
  const data = getUserExportData(userId);
  const file = path.join(os.tmpdir(), `bill-tracker-user-${userId}-${Date.now()}.sqlite`);
  const out = new Database(file);
  try {
    out.exec(`
      CREATE TABLE export_metadata (key TEXT PRIMARY KEY, value TEXT);
      CREATE TABLE categories (id INTEGER PRIMARY KEY, name TEXT, created_at TEXT, updated_at TEXT);
      CREATE TABLE bills (id INTEGER PRIMARY KEY, name TEXT, category_id INTEGER, due_day INTEGER, override_due_date TEXT, bucket TEXT, expected_amount REAL, interest_rate REAL, billing_cycle TEXT, cycle_type TEXT, cycle_day TEXT, autopay_enabled INTEGER, autodraft_status TEXT, website TEXT, username TEXT, account_info TEXT, has_2fa INTEGER, active INTEGER, notes TEXT, created_at TEXT, updated_at TEXT);
      CREATE TABLE payments (id INTEGER PRIMARY KEY, bill_id INTEGER, amount REAL, paid_date TEXT, method TEXT, notes TEXT, payment_source TEXT, transaction_id INTEGER, created_at TEXT, updated_at TEXT);
      CREATE TABLE monthly_bill_state (id INTEGER PRIMARY KEY, bill_id INTEGER, year INTEGER, month INTEGER, actual_amount REAL, notes TEXT, is_skipped INTEGER, created_at TEXT, updated_at TEXT);
      CREATE TABLE monthly_starting_amounts (id INTEGER PRIMARY KEY, year INTEGER, month INTEGER, first_amount REAL, fifteenth_amount REAL, other_amount REAL, notes TEXT, created_at TEXT, updated_at TEXT);
      CREATE TABLE bill_history_ranges (id INTEGER PRIMARY KEY, bill_id INTEGER, start_year INTEGER, start_month INTEGER, end_year INTEGER, end_month INTEGER, label TEXT, created_at TEXT, updated_at TEXT);
      CREATE TABLE notes (type TEXT, bill_id INTEGER, payment_id INTEGER, monthly_state_id INTEGER, year INTEGER, month INTEGER, notes TEXT);
    `);
    const meta = out.prepare('INSERT INTO export_metadata (key, value) VALUES (?, ?)');
    meta.run('metadata_json', JSON.stringify(data.metadata));
    const insertRows = (table, rows) => {
      if (!rows.length) return;
      const cols = Object.keys(rows[0]);
      const stmt = out.prepare(
        `INSERT INTO ${table} (${cols.join(',')}) VALUES (${cols.map(() => '?').join(',')})`,
      );
      const tx = out.transaction((items) =>
        items.forEach((row) => stmt.run(cols.map((c) => row[c]))),
      );
      tx(rows);
    };
    insertRows('categories', data.categories);
    insertRows('bills', data.bills);
    insertRows('payments', data.payments);
    insertRows('monthly_bill_state', data.monthly_bill_state);
    insertRows('monthly_starting_amounts', data.monthly_starting_amounts);
    insertRows('bill_history_ranges', data.bill_history_ranges);
    insertRows(
      'notes',
      data.notes.map((n) => ({
        type: n.type,
        bill_id: n.bill_id ?? null,
        payment_id: n.payment_id ?? null,
        monthly_state_id: n.monthly_state_id ?? null,
        year: n.year ?? null,
        month: n.month ?? null,
        notes: n.notes,
      })),
    );
  } finally {
    out.close();
  }
  return file;
}

router.get('/user-db', (req: Req, res: Res) => {
  const file = buildUserDbExportFile(req.user.id);
  res.download(file, 'bill-tracker-user-export.sqlite', () => {
    try {
      fs.unlinkSync(file);
    } catch {}
  });
});

// Full portable JSON export of the user's data (same assembly as SQLite/Excel).
router.get('/user-json', (req: Req, res: Res) => {
  const data = getUserExportData(req.user.id);
  res.setHeader('Content-Type', 'application/json');
  res.setHeader('Content-Disposition', 'attachment; filename="bill-tracker-user-export.json"');
  res.send(JSON.stringify(data, null, 2));
});

module.exports = router;
module.exports.buildUserDbExportFile = buildUserDbExportFile;
module.exports.getUserExportData = getUserExportData;
