import dotenv from 'dotenv';
dotenv.config({ path: '/var/www/html/sportsclaw-guru/backend/.env' });

import {
  generateDailyGamePreviews,
  generatePropsAnalysis,
  generateLineMovementAlert,
  generateWeeklyRecap,
  generateEducationalGuide,
} from './SportsClawSEOAgent';
import { getActiveLeagues, hasJobStartedOnEtDate } from './data-queries';

const INTERVAL_MS = 60 * 60 * 1000; // 1 hour
let running = false;
let shutdownRequested = false;
let nextTimer: NodeJS.Timeout | null = null;

function getETParts(now: Date = new Date()) {
  const formatter = new Intl.DateTimeFormat('en-US', {
    timeZone: 'America/New_York',
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    weekday: 'long',
    hour: '2-digit',
    hourCycle: 'h23',
  });
  const parts = formatter.formatToParts(now);
  const map = Object.fromEntries(parts.map((part) => [part.type, part.value]));
  const weekdayMap: Record<string, number> = {
    Sunday: 0,
    Monday: 1,
    Tuesday: 2,
    Wednesday: 3,
    Thursday: 4,
    Friday: 5,
    Saturday: 6,
  };

  return {
    hour: parseInt(map.hour, 10),
    day: weekdayMap[map.weekday] ?? now.getDay(),
    etDate: `${map.year}-${map.month}-${map.day}`,
  };
}

async function shouldRunDailyJob(jobType: string, earliestHour: number, now: Date): Promise<boolean> {
  const { hour } = getETParts(now);
  if (hour < earliestHour) return false;
  return !(await hasJobStartedOnEtDate(jobType, now));
}

async function runScheduledJobs(): Promise<void> {
  if (running) {
    console.log('[worker] Previous cycle still running, skipping');
    return;
  }
  running = true;

  const now = new Date();
  const { hour, day, etDate } = getETParts(now);
  const today = new Date();
  let totalGenerated = 0;

  console.log(`[worker] === Cycle start === ET hour=${hour}, day=${day}, date=${etDate}`);

  try {
    // Daily jobs recover after PM2 restarts instead of requiring an exact hour match.
    if (await shouldRunDailyJob('game-preview', 6, now)) {
      console.log('[worker] Running: daily game previews');
      const count = await generateDailyGamePreviews(today);
      totalGenerated += count;
      console.log(`[worker] Game previews generated: ${count}`);
    }

    if (await shouldRunDailyJob('props-analysis', 7, now)) {
      console.log('[worker] Running: props analysis');
      const leagues = await getActiveLeagues();
      for (const league of leagues) {
        if (shutdownRequested) break;
        const count = await generatePropsAnalysis(league, today);
        totalGenerated += count;
      }
      console.log(`[worker] Props analysis generated: ${totalGenerated}`);
    }

    // Line movement alerts — every 4 hours (8, 12, 16, 20)
    if (hour % 4 === 0 && hour >= 8 && hour <= 20) {
      console.log('[worker] Running: line movement alerts');
      const leagues = await getActiveLeagues();
      for (const league of leagues) {
        if (shutdownRequested) break;
        const count = await generateLineMovementAlert(league);
        totalGenerated += count;
      }
      console.log(`[worker] Line movement alerts generated: ${totalGenerated}`);
    }

    if (day === 1 && await shouldRunDailyJob('weekly-recap', 8, now)) {
      console.log('[worker] Running: weekly recaps');
      const leagues = await getActiveLeagues();
      for (const league of leagues) {
        if (shutdownRequested) break;
        const count = await generateWeeklyRecap(league);
        totalGenerated += count;
      }
      console.log(`[worker] Weekly recaps generated: ${totalGenerated}`);
    }

    if ((day === 3 || day === 6) && await shouldRunDailyJob('guide', 10, now)) {
      console.log('[worker] Running: educational guide');
      const count = await generateEducationalGuide();
      totalGenerated += count;
    }
  } catch (error) {
    console.error('[worker] Cycle error:', error);
  }

  console.log(`[worker] === Cycle end === total generated: ${totalGenerated}`);
  running = false;
}

function msUntilNextHour(now: Date = new Date()): number {
  const next = new Date(now);
  next.setMinutes(0, 0, 0);
  next.setHours(next.getHours() + 1);
  return Math.max(1000, next.getTime() - now.getTime());
}

function scheduleNextRun(): void {
  if (shutdownRequested) return;
  const delay = msUntilNextHour();
  console.log(`[worker] Next cycle in ${Math.round(delay / 1000)}s`);
  nextTimer = setTimeout(async () => {
    await runScheduledJobs();
    scheduleNextRun();
  }, delay);
}

// Graceful shutdown
function shutdown(signal: string) {
  console.log(`[worker] Received ${signal}, shutting down gracefully...`);
  shutdownRequested = true;
  if (nextTimer) {
    clearTimeout(nextTimer);
    nextTimer = null;
  }
  // Give running tasks up to 30 seconds
  setTimeout(() => {
    console.log('[worker] Forced shutdown after timeout');
    process.exit(0);
  }, 30000);
}

process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));

// Main loop
async function main() {
  console.log('[worker] SportsClaw SEO Content Worker started');
  console.log(`[worker] Schedule: previews@6AM, props@7AM, line-moves@4h, recaps@Mon8AM, guides@Wed+Sat10AM`);
  console.log(`[worker] Interval: ${INTERVAL_MS / 60000} minutes`);

  // Run immediately on start
  await runScheduledJobs();

  // Then align future runs to the top of each hour so PM2 restarts do not shift the schedule.
  scheduleNextRun();
}

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