/**
 * Tourist Pass Expiration Worker
 *
 * Long-lived PM2 process that runs every 10 minutes.
 * Expires tourist passes where expires_at_utc <= now:
 * 1. Marks rm_tourist_passes status = 'EXPIRED'
 * 2. Zeros out daily_pass_picks on rm_users
 * 3. Clears tourist_pass_expires_at on rm_users
 * 4. Logs TOURIST_EXPIRE ledger entry
 *
 * Usage: npx tsx src/workers/tourist-pass-expiry.ts
 */

import 'dotenv/config';
import { Pool } from 'pg';
import { buildDatabasePoolConfig } from '../db/config';

const CYCLE_INTERVAL_MS = 600_000; // 10 minutes

const pool = new Pool(buildDatabasePoolConfig({ max: 2 }));

pool.on('error', (err) => {
  console.error('[tourist-pass-expiry] DB pool error:', err);
});

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

async function expiryCycle(): Promise<void> {
  const cycleStart = Date.now();

  // Find all ACTIVE passes that have expired
  const { rows: expiredPasses } = await pool.query(
    `SELECT tp.id AS pass_id, tp.user_id, tp.expires_at_utc, tp.purchase_timezone_iana, tp.credits_granted
     FROM rm_tourist_passes tp
     WHERE tp.status = 'ACTIVE' AND tp.expires_at_utc <= NOW()`
  );

  if (expiredPasses.length === 0) {
    return;
  }

  console.log(`[tourist-pass-expiry] Found ${expiredPasses.length} expired passes to process`);

  let expired = 0;
  for (const pass of expiredPasses) {
    try {
      // 1. Mark pass as EXPIRED
      await pool.query(
        `UPDATE rm_tourist_passes SET status = 'EXPIRED' WHERE id = $1 AND status = 'ACTIVE'`,
        [pass.pass_id]
      );

      // 2. Zero out daily pass credits on rm_users and clear expiration
      const { rows: userRows } = await pool.query(
        `UPDATE rm_users
         SET daily_pass_picks = 0,
             tourist_pass_expires_at = NULL,
             tourist_pass_timezone = NULL,
             tourist_pass_timezone_source = NULL,
             updated_at = NOW()
         WHERE id = $1
         RETURNING daily_free_forecasts, single_picks`,
        [pass.user_id]
      );

      // 3. Log TOURIST_EXPIRE ledger entry
      const balanceAfter = userRows[0]
        ? (userRows[0].daily_free_forecasts || 0) + (userRows[0].single_picks || 0)
        : 0;

      await pool.query(
        `INSERT INTO rm_forecast_ledger (user_id, change_amount, reason, balance_after, metadata)
         VALUES ($1, $2, 'TOURIST_EXPIRE', $3, $4)`,
        [
          pass.user_id,
          -pass.credits_granted,
          balanceAfter,
          JSON.stringify({
            pass_id: pass.pass_id,
            timezone: pass.purchase_timezone_iana,
            expired_at: new Date().toISOString(),
          }),
        ]
      );

      expired++;
      console.log(`  [EXPIRED] user=${pass.user_id} pass=${pass.pass_id} tz=${pass.purchase_timezone_iana}`);
    } catch (err: any) {
      console.error(`  [FAIL] pass=${pass.pass_id}: ${err.message}`);
    }
  }

  const elapsed = Math.round((Date.now() - cycleStart) / 1000);
  console.log(`[tourist-pass-expiry] Cycle done: ${expired} expired in ${elapsed}s`);
}

// Also handle legacy passes (no tourist_pass_expires_at but daily_pass_date is stale)
async function legacyExpiryCycle(): Promise<void> {
  // Find users with daily_pass_picks > 0 but no tourist_pass_expires_at and stale daily_pass_date
  const todayET = new Date().toLocaleDateString('en-CA', { timeZone: 'America/New_York' });
  const etNow = new Date(new Date().toLocaleString('en-US', { timeZone: 'America/New_York' }));
  const etHour = etNow.getHours();

  // Only expire legacy passes after 2 AM ET
  if (etHour < 2) return;

  const yesterdayET = new Date(etNow);
  yesterdayET.setDate(yesterdayET.getDate() - 1);
  const yesterdayStr = yesterdayET.toISOString().split('T')[0];

  const { rows } = await pool.query(
    `SELECT id, daily_pass_picks FROM rm_users
     WHERE daily_pass_picks > 0
       AND tourist_pass_expires_at IS NULL
       AND daily_pass_date IS NOT NULL
       AND daily_pass_date < $1::date`,
    [yesterdayStr]
  );

  for (const user of rows) {
    try {
      await pool.query(
        `UPDATE rm_users SET daily_pass_picks = 0, updated_at = NOW() WHERE id = $1`,
        [user.id]
      );
      await pool.query(
        `INSERT INTO rm_forecast_ledger (user_id, change_amount, reason, balance_after, metadata)
         VALUES ($1, $2, 'TOURIST_EXPIRE', 0, $3)`,
        [user.id, -user.daily_pass_picks, JSON.stringify({ source: 'legacy_expiry' })]
      );
      console.log(`  [LEGACY-EXPIRED] user=${user.id} picks=${user.daily_pass_picks}`);
    } catch (err: any) {
      console.error(`  [LEGACY-FAIL] user=${user.id}: ${err.message}`);
    }
  }
}

let shuttingDown = false;
process.on('SIGTERM', () => { console.log('[tourist-pass-expiry] SIGTERM received, finishing cycle...'); shuttingDown = true; });
process.on('SIGINT', () => { console.log('[tourist-pass-expiry] SIGINT received, finishing cycle...'); shuttingDown = true; });

async function main() {
  console.log('='.repeat(60));
  console.log('TOURIST PASS EXPIRATION WORKER');
  console.log(`Started: ${new Date().toISOString()}`);
  console.log(`Cycle interval: ${CYCLE_INTERVAL_MS / 1000}s`);
  console.log('='.repeat(60));

  while (!shuttingDown) {
    try {
      await expiryCycle();
      await legacyExpiryCycle();
    } catch (err: any) {
      console.error(`[tourist-pass-expiry] Cycle error: ${err.message}`);
    }
    if (!shuttingDown) await sleep(CYCLE_INTERVAL_MS);
  }
  console.log('[tourist-pass-expiry] Graceful shutdown complete');
  process.exit(0);
}

main().catch((err) => {
  console.error('[tourist-pass-expiry] Fatal error:', err);
  process.exit(1);
});
