// @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 router = express.Router();
const fs = require('fs');
const os = require('os');
const { getDb, getSetting } = require('../db/database.cts');
const { getStatusRuntime, recordError } = require('../services/statusRuntime.cts');
const { listBackups } = require('../services/backupService.cts');
const { getScheduleStatus } = require('../services/backupScheduler.cts');
const { checkForUpdates } = require('../services/updateCheckService.cts');
const { getStatus: getBankSyncWorkerStatus } = require('../services/bankSyncWorker.cts');
const { getBankSyncConfig } = require('../services/bankSyncConfigService.cts');
const { accountingActiveSql } = require('../services/paymentAccountingService.cts');
const { localDateString } = require('../utils/dates.mts');

const startTime = Date.now();
let pkg;
try {
  pkg = require('../package.json');
} catch {
  pkg = { name: 'bill-tracker', version: '1.0.0' };
}

function errorMessage(err) {
  return err?.message || String(err);
}

// SQLite stores datetime('now') as "YYYY-MM-DD HH:MM:SS" (UTC, no Z).
// Without the Z suffix, JS Date parses it as LOCAL time, creating timezone
// inconsistencies when mixed with ISO strings that do have Z.
// This ensures all timestamps sent to clients are unambiguous UTC ISO strings.
function toIso(sqliteOrIso) {
  if (!sqliteOrIso) return null;
  const s = String(sqliteOrIso).trim();
  // Already ISO with Z or offset — return as-is
  if (s.includes('T') && (s.endsWith('Z') || s.match(/[+-]\d{2}:\d{2}$/))) return s;
  // SQLite format "YYYY-MM-DD HH:MM:SS" — append Z to declare it UTC
  if (/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/.test(s)) return s.replace(' ', 'T') + 'Z';
  return s;
}

function monthRange(now) {
  const year = now.getFullYear();
  const month = now.getMonth() + 1;
  const daysInMonth = new Date(year, month, 0).getDate();
  const monthStr = String(month).padStart(2, '0');
  return {
    year,
    month,
    start: `${year}-${monthStr}-01`,
    end: `${year}-${monthStr}-${String(daysInMonth).padStart(2, '0')}`,
  };
}

// GET /api/status
router.get('/', async (req: Req, res: Res) => {
  const runtimeState = getStatusRuntime();
  const now = new Date();
  const uptimeSeconds = Math.floor((Date.now() - startTime) / 1000);

  let db;
  let database = {
    ok: false,
    connected: false,
    status: 'error',
    size_bytes: 0,
    size_mb: '0.00',
    last_modified: null,
    last_error: null,
  };
  let stats = {
    active_sessions: null,
    users: null,
    active_bills: null,
    total_payments: null,
  };
  let notifications = {
    ok: true,
    enabled: false,
    configured: false,
    smtp_configured: false,
    last_test_at: runtimeState.notifications.last_test_at,
    last_sent_at: null,
    last_error: runtimeState.notifications.last_error,
  };
  let backups = {
    ok: true,
    enabled: false,
    configured: false,
    last_backup_at: null,
    last_backup_path: null,
    backup_count: null,
    keep_count: null,
    last_error: null,
  };
  let tracker = {
    ok: true,
    current_year: now.getFullYear(),
    current_month: now.getMonth() + 1,
    bill_count: null,
    payment_count: null,
    bills_this_month: null,
    payments_this_month: null,
    overdue_count: null,
    skipped_count: null,
    last_error: null,
  };

  try {
    db = getDb();
    db.prepare('SELECT 1 AS ok').get();

    const dbPath = db.name;
    let dbSizeBytes = 0;
    let dbLastModified = null;

    try {
      const stat = fs.statSync(dbPath);
      dbSizeBytes = stat.size;
      dbLastModified = stat.mtime.toISOString();
    } catch {
      // The DB may be in-memory or on a volume that cannot be stat'ed.
    }

    database = {
      ok: true,
      connected: true,
      status: 'connected',
      // Filesystem path intentionally omitted — not safe to expose to all users
      size_bytes: dbSizeBytes,
      size_mb: (dbSizeBytes / 1024 / 1024).toFixed(2),
      last_modified: dbLastModified,
      last_error: null,
    };
  } catch (err) {
    database.last_error = errorMessage(err);
    recordError('Database', err);
  }

  if (db) {
    try {
      stats = {
        active_sessions: db
          .prepare("SELECT COUNT(*) AS n FROM sessions WHERE expires_at > datetime('now')")
          .get().n,
        users: db.prepare('SELECT COUNT(*) AS n FROM users').get().n,
        active_bills: db.prepare('SELECT COUNT(*) AS n FROM bills WHERE active = 1').get().n,
        total_payments: db.prepare('SELECT COUNT(*) AS n FROM payments').get().n,
      };
    } catch (err) {
      recordError('Status Statistics', err);
    }

    try {
      const enabled = getSetting('notify_smtp_enabled') === 'true';
      const host = getSetting('notify_smtp_host');
      const sender = getSetting('notify_sender_address');
      const port = getSetting('notify_smtp_port');
      const username = getSetting('notify_smtp_username');
      const password = getSetting('notify_smtp_password');
      const configured = !!(host && sender && port && (!username || password));
      const lastSent =
        db.prepare('SELECT MAX(sent_date) AS sent_date FROM notifications').get()?.sent_date ||
        null;

      notifications = {
        ...notifications,
        ok: (!enabled || configured) && !notifications.last_error,
        enabled,
        configured,
        smtp_configured: configured,
        last_sent_at: lastSent,
      };
    } catch (err) {
      notifications = {
        ...notifications,
        ok: false,
        last_error: errorMessage(err),
      };
      recordError('Notifications Status', err);
    }

    try {
      const enabled = getSetting('backup_schedule_enabled') === 'true';
      const keepCount = parseInt(getSetting('backup_keep_count') || '', 10);
      const managedBackups = listBackups();
      const latestBackup = managedBackups[0] || null;
      const schedule = getScheduleStatus();

      backups = {
        ok: true,
        enabled,
        configured: true,
        scheduled_enabled: schedule.enabled,
        scheduled_frequency: schedule.frequency,
        scheduled_time: schedule.time,
        next_backup_at: schedule.next_run_at,
        last_backup_at: latestBackup?.modified_at || null,
        last_backup_path: latestBackup?.id || null,
        last_backup_id: latestBackup?.id || null,
        backup_count: managedBackups.length,
        count: managedBackups.length,
        keep_count: schedule.retention_count ?? (Number.isInteger(keepCount) ? keepCount : null),
        retention_count:
          schedule.retention_count ?? (Number.isInteger(keepCount) ? keepCount : null),
        last_error: schedule.last_error || null,
      };
    } catch (err) {
      backups = {
        ...backups,
        ok: false,
        last_error: errorMessage(err),
      };
      recordError('Backups Status', err);
    }

    try {
      const range = monthRange(now);
      const todayDay = now.getDate();
      const billCount = db.prepare('SELECT COUNT(*) AS n FROM bills WHERE active = 1').get().n;
      const paymentCount = db
        .prepare(
          `SELECT COUNT(*) AS n FROM payments WHERE paid_date BETWEEN ? AND ? AND deleted_at IS NULL AND ${accountingActiveSql()}`,
        )
        .get(range.start, range.end).n;
      const skippedCount = db
        .prepare(
          'SELECT COUNT(*) AS n FROM monthly_bill_state WHERE year = ? AND month = ? AND is_skipped = 1',
        )
        .get(range.year, range.month).n;
      const overdueCount = db
        .prepare(
          `
        SELECT COUNT(*) AS n FROM bills b
        WHERE b.active = 1
          AND COALESCE(NULLIF(b.cycle_type, ''), 'monthly') = 'monthly'
          AND CAST(b.due_day AS INTEGER) < ?
          AND NOT EXISTS (
            SELECT 1 FROM payments p
            WHERE p.bill_id = b.id AND p.paid_date BETWEEN ? AND ? AND p.deleted_at IS NULL AND ${accountingActiveSql('p')}
          )
          AND NOT EXISTS (
            SELECT 1 FROM monthly_bill_state mbs
            WHERE mbs.bill_id = b.id AND mbs.year = ? AND mbs.month = ? AND mbs.is_skipped = 1
          )
      `,
        )
        .get(todayDay, range.start, range.end, range.year, range.month).n;

      tracker = {
        ok: true,
        current_year: range.year,
        current_month: range.month,
        bill_count: billCount,
        payment_count: paymentCount,
        bills_this_month: billCount,
        payments_this_month: paymentCount,
        overdue_count: overdueCount,
        skipped_count: skippedCount,
        last_error: null,
      };
    } catch (err) {
      tracker = {
        ...tracker,
        ok: false,
        last_error: errorMessage(err),
      };
      recordError('Tracker Status', err);
    }
  }

  // Bank sync (SimpleFIN) status
  let bankSync = {
    enabled: false,
    running: false,
    source_count: 0,
    account_count: 0,
    transaction_count: 0,
    interval_hours: null,
    sync_days: null,
    last_sync_at: null,
    next_run_at: null,
    last_error: null,
  };
  try {
    const config = getBankSyncConfig();
    const workerStatus = getBankSyncWorkerStatus();
    bankSync = {
      enabled: config.enabled,
      running: workerStatus.running,
      interval_hours: config.sync_interval_hours,
      sync_days: config.sync_days,
      source_count: 0,
      account_count: 0,
      transaction_count: 0,
      last_sync_at: null,
      next_run_at: workerStatus.next_run_at,
      last_error: null,
    };
    if (db) {
      const sourceRow = db
        .prepare(
          `
        SELECT COUNT(*) AS source_count, MAX(last_sync_at) AS last_sync_at
        FROM data_sources WHERE type = 'provider_sync' AND provider = 'simplefin'
      `,
        )
        .get();
      const accountRow = db
        .prepare(
          `
        SELECT COUNT(fa.id) AS account_count
        FROM financial_accounts fa
        INNER JOIN data_sources ds ON ds.id = fa.data_source_id
        WHERE ds.type = 'provider_sync' AND ds.provider = 'simplefin'
      `,
        )
        .get();
      const txRow = db
        .prepare(
          `
        SELECT COUNT(t.id) AS transaction_count
        FROM transactions t
        INNER JOIN data_sources ds ON ds.id = t.data_source_id
        WHERE ds.type = 'provider_sync' AND ds.provider = 'simplefin'
      `,
        )
        .get();
      const errorRow = db
        .prepare(
          `
        SELECT last_error FROM data_sources
        WHERE type = 'provider_sync' AND provider = 'simplefin'
          AND status = 'error' AND last_error IS NOT NULL
        ORDER BY updated_at DESC LIMIT 1
      `,
        )
        .get();
      bankSync = {
        ...bankSync,
        source_count: sourceRow.source_count ?? 0,
        account_count: accountRow.account_count ?? 0,
        transaction_count: txRow.transaction_count ?? 0,
        last_sync_at: toIso(sourceRow.last_sync_at),
        last_error: errorRow?.last_error || null,
      };
    }
  } catch (err) {
    recordError('Bank Sync Status', err);
  }

  // Cleanup status — safe read-only summary from settings, no paths or secrets
  let cleanup = { ok: true, last_run_at: null, last_result: null };
  try {
    const raw = getSetting('cleanup_last_result');
    cleanup = {
      ok: true,
      last_run_at: toIso(getSetting('cleanup_last_run_at')),
      last_result: raw ? JSON.parse(raw) : null,
    };
  } catch {
    /* non-fatal */
  }

  const memoryMb = Number((process.memoryUsage().rss / 1024 / 1024).toFixed(1));
  const runtime = {
    ok: true,
    node: process.version,
    node_version: process.version,
    platform: process.platform,
    arch: process.arch,
    uptime_seconds: uptimeSeconds,
    memory_mb: memoryMb,
  };
  const server = {
    ok: true,
    time: now.toISOString(),
    now: now.toISOString(),
    today: localDateString(now),
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || process.env.TZ || null,
    utc_offset: -now.getTimezoneOffset() / 60,
    env: process.env.NODE_ENV || 'development',
  };
  const worker = {
    ok: !runtimeState.worker.last_error,
    enabled: runtimeState.worker.enabled,
    running: runtimeState.worker.running,
    started_at: runtimeState.worker.started_at,
    last_run_at: runtimeState.worker.last_run_at,
    last_success_at: runtimeState.worker.last_run_at,
    next_run_at: runtimeState.worker.next_run_at,
    last_error: runtimeState.worker.last_error,
  };

  // Update check — non-blocking; uses cached result if available
  let update = {
    current_version: pkg.version,
    latest_version: null,
    up_to_date: null,
    has_update: false,
    error: null,
    last_checked_at: null,
  };
  try {
    update = await checkForUpdates();
  } catch {
    /* non-fatal */
  }

  const recentErrors = getStatusRuntime().recentErrors;
  const ok = database.ok && tracker.ok;

  res.json({
    ok,
    app: {
      name: pkg.name,
      version: pkg.version,
      environment: server.env,
      uptime_seconds: uptimeSeconds,
    },
    runtime,
    database,
    db: database,
    stats,
    statistics: stats,
    worker,
    notifications,
    backups,
    backup: backups,
    server,
    tracker,
    bank_sync: bankSync,
    simplefin: bankSync,
    cleanup,
    update,
    recent_errors: recentErrors,
    errors: recentErrors,
    version: pkg.version,
    environment: server.env,
    uptime_seconds: uptimeSeconds,
    node_version: process.version,
    platform: process.platform,
    hostname: os.hostname(),
  });
});

module.exports = router;
