/**
 * Rainmaker Twitter Agent Worker V2
 * Upgraded from broadcast bot → distribution + conversion engine.
 *
 * Optimized Schedule (ET):
 *   7 AM   — Daily slate (upgraded: best edge, trap watch, dogs)
 *   9 AM   — Forecast thread (anchor + reply chain)
 *   11 AM  — Educational content (authority building, topic rotation)
 *   1 PM   — Forecast thread (midday)
 *   3 PM   — PIFF prop forecasts (2-3 props)
 *   5 PM   — CTA / conversion tweet
 *   6 PM   — Prime time reminder (pre-game edges)
 *   10 PM  — Daily recap (with momentum flex + season record)
 *
 * Reactive (every cycle, 9 AM–10 PM):
 *   — Live edge alerts (high-confidence games starting in 1-4 hours)
 *   — Momentum post (auto-triggered when 3+ win streak detected)
 *
 * Engagement (8 AM–11 PM, every cycle):
 *   — Smarter likes (intent-based + matchup-aware queries)
 */
import dotenv from 'dotenv';
dotenv.config({ path: '/var/www/html/rainmaker/backend/.env' });

import {
  generateForecastPicks,
  generateForecastThread,
  generatePiffPropPicks,
  generateDailySlate,
  generateDailyRecap,
  generateMomentumPost,
  generateLiveEdgeAlert,
  generatePrimeTimeReminder,
  generateCTAPost,
  generateEducationalPost,
} from './RainmakerContentGenerator';
import { postTweet } from './services/twitter-api.service';
import { runEngagementCycle, processMentions } from './services/twitter-engagement.service';
import {
  insertTweet,
  getTweetCountLast24Hours,
  getEngagementCountLast24Hours,
  hasSlotExecuted,
  insertWorkerRun,
  getContentTypeCountToday,
} from './twitter-data-queries';
import type { TweetContent } from './types';

const INTERVAL_MS = 30 * 60 * 1000; // 30 minutes
const MAX_TWEETS_24H = parseInt(process.env.RAINMAKER_MAX_TWEETS_24H || '40');
const INTER_TWEET_DELAY_MS = 3000;
const POST_ONLY_MODE = process.env.RAINMAKER_POST_ONLY_MODE === 'true';
const CREDITS_DEPLETED_BACKOFF_HOURS = parseInt(process.env.RAINMAKER_TWITTER_402_BACKOFF_HOURS || '6', 10);

let running = false;
let shutdownRequested = false;
let creditsDepletedUntilMs = 0;

// ── ET Timezone Helpers ──────────────────────────────────────

function getETHour(): number {
  const etString = new Date().toLocaleString('en-US', { timeZone: 'America/New_York', hour: 'numeric', hour12: false });
  const h = parseInt(etString);
  return h === 24 ? 0 : h;
}

function isCreditsDepletedError(err: unknown): boolean {
  const message = err instanceof Error ? err.message : String(err || '');
  return message.includes('CreditsDepleted') || message.includes('failed 402');
}

function isPostingPausedForCredits(): boolean {
  return creditsDepletedUntilMs > Date.now();
}

function noteCreditsDepleted(): void {
  const nextPauseMs = Date.now() + CREDITS_DEPLETED_BACKOFF_HOURS * 60 * 60 * 1000;
  if (nextPauseMs > creditsDepletedUntilMs) {
    creditsDepletedUntilMs = nextPauseMs;
  }
  console.error(`[rm-worker] Twitter credits depleted. Pausing posting until ${new Date(creditsDepletedUntilMs).toISOString()}`);
}

// ── Tweet Processing ─────────────────────────────────────────

async function processTweet(tweet: TweetContent): Promise<string | null> {
  try {
    const count = await getTweetCountLast24Hours();
    if (count >= MAX_TWEETS_24H) {
      console.log(`[rm-worker] 24h tweet limit reached (${count}/${MAX_TWEETS_24H}), skipping`);
      return null;
    }

    const result = await postTweet(tweet.text);

    await insertTweet(
      result.tweetId,
      tweet.contentType,
      tweet.text,
      tweet.league,
      tweet.forecastId,
      tweet.gameKey,
      tweet.pickDirection,
      result.tweetId.startsWith('dry_') ? 'preview' : 'posted'
    );

    console.log(`[rm-worker] Posted tweet ${result.tweetId}: "${tweet.text.substring(0, 60)}..."`);
    return result.tweetId;
  } catch (err) {
    if (isCreditsDepletedError(err)) {
      noteCreditsDepleted();
      throw err;
    }
    console.error(`[rm-worker] Tweet failed:`, (err as Error).message);
    return null;
  }
}

async function processTweets(tweets: TweetContent[]): Promise<number> {
  let posted = 0;
  for (const tweet of tweets) {
    if (shutdownRequested) break;
    const tweetId = await processTweet(tweet);
    if (tweetId) posted++;
    await new Promise(r => setTimeout(r, INTER_TWEET_DELAY_MS));
  }
  return posted;
}

// ── Thread Processing (anchor + self-replies) ────────────────

async function processThread(tweets: TweetContent[]): Promise<number> {
  if (tweets.length === 0) return 0;

  // Post anchor tweet
  const anchorId = await processTweet(tweets[0]);
  if (!anchorId) return 0;

  let posted = 1;
  let lastTweetId = anchorId;

  // Post replies as self-reply chain
  for (let i = 1; i < tweets.length; i++) {
    if (shutdownRequested) break;
    await new Promise(r => setTimeout(r, INTER_TWEET_DELAY_MS));

    try {
      const count = await getTweetCountLast24Hours();
      if (count >= MAX_TWEETS_24H) {
        console.log(`[rm-worker] 24h tweet limit reached during thread, stopping`);
        break;
      }

      const result = await postTweet(tweets[i].text, lastTweetId);

      await insertTweet(
        result.tweetId,
        tweets[i].contentType,
        tweets[i].text,
        tweets[i].league,
        tweets[i].forecastId,
        tweets[i].gameKey,
        tweets[i].pickDirection,
        result.tweetId.startsWith('dry_') ? 'preview' : 'posted'
      );

      console.log(`[rm-worker] Thread reply ${i}/${tweets.length - 1}: ${result.tweetId}`);
      lastTweetId = result.tweetId;
      posted++;
    } catch (err) {
      if (isCreditsDepletedError(err)) {
        noteCreditsDepleted();
        throw err;
      }
      console.error(`[rm-worker] Thread reply ${i} failed:`, (err as Error).message);
      // Don't break — try remaining replies
    }
  }

  return posted;
}

// ── Slot Executor ────────────────────────────────────────────

async function executeSlot(slotName: string, handler: () => Promise<number>): Promise<number> {
  if (await hasSlotExecuted(slotName)) {
    console.log(`[rm-worker] Slot ${slotName} already executed today, skipping`);
    return 0;
  }

  console.log(`[rm-worker] Executing slot: ${slotName}`);
  try {
    const count = await handler();
    await insertWorkerRun(slotName, count);
    console.log(`[rm-worker] Slot ${slotName} completed — ${count} tweets`);
    return count;
  } catch (err) {
    if (isCreditsDepletedError(err)) {
      console.warn(`[rm-worker] Slot ${slotName} deferred due to depleted Twitter credits`);
      return 0;
    }
    console.error(`[rm-worker] Slot ${slotName} failed:`, (err as Error).message);
    await insertWorkerRun(slotName, 0, 'failed');
    return 0;
  }
}

// ── Main Cycle ───────────────────────────────────────────────

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

  const hour = getETHour();
  let totalPosted = 0;

  console.log(`[rm-worker] === Cycle start === ET hour=${hour}`);

  try {
    if (isPostingPausedForCredits()) {
      console.warn(`[rm-worker] Posting paused until ${new Date(creditsDepletedUntilMs).toISOString()} due to depleted Twitter credits`);
    } else {
      // ─── SCHEDULED SLOTS ──────────────────────────────────

      // 7 AM: Daily slate (upgraded with best edge / trap / dogs)
      if (hour === 7) {
        totalPosted += await executeSlot('daily_slate_07', async () => {
          const tweets = await generateDailySlate();
          return processTweets(tweets);
        });
      }

      // 9 AM: Forecast thread (highest confidence game)
      if (hour === 9) {
        totalPosted += await executeSlot('forecast_thread_09', async () => {
          const tweets = await generateForecastThread();
          if (tweets.length > 1) {
            return processThread(tweets);
          }
          // Fallback: single tweet if thread gen failed
          return processTweets(tweets);
        });
      }

      // 11 AM: Educational content (authority building)
      if (hour === 11) {
        totalPosted += await executeSlot('educational_11', async () => {
          const tweets = await generateEducationalPost();
          return processTweets(tweets);
        });
      }

      // 1 PM: Midday forecast thread
      if (hour === 13) {
        totalPosted += await executeSlot('forecast_thread_13', async () => {
          const tweets = await generateForecastThread();
          if (tweets.length > 1) {
            return processThread(tweets);
          }
          return processTweets(tweets);
        });
      }

      // 3 PM: PIFF prop forecasts
      if (hour === 15) {
        totalPosted += await executeSlot('piff_prop_15', async () => {
          const tweets = await generatePiffPropPicks();
          return processTweets(tweets);
        });
      }

      // 5 PM: CTA / conversion tweet
      if (hour === 17) {
        totalPosted += await executeSlot('cta_17', async () => {
          const tweets = await generateCTAPost();
          return processTweets(tweets);
        });
      }

      // 6 PM: Prime time reminder (pre-game edges)
      if (hour === 18) {
        totalPosted += await executeSlot('prime_time_18', async () => {
          const tweets = await generatePrimeTimeReminder();
          return processTweets(tweets);
        });
      }

      // 10 PM: Daily recap (with momentum + season record)
      if (hour === 22) {
        totalPosted += await executeSlot('daily_recap_22', async () => {
          const tweets = await generateDailyRecap();
          return processTweets(tweets);
        });
      }

      // ─── REACTIVE SLOTS (every cycle, 9 AM–10 PM) ────────

      if (hour >= 9 && hour <= 22) {
        // Live edge alerts: high-confidence games starting soon
        try {
          const edgeAlerts = await generateLiveEdgeAlert();
          if (edgeAlerts.length > 0) {
            const edgePosted = await processTweets(edgeAlerts);
            totalPosted += edgePosted;
            if (edgePosted > 0) {
              console.log(`[rm-worker] Posted ${edgePosted} live edge alert(s)`);
            }
          }
        } catch (err) {
          console.error('[rm-worker] Live edge alert failed:', (err as Error).message);
        }

        // Momentum post: auto-trigger when streak detected (max 1/day)
        try {
          const momentumCountToday = await getContentTypeCountToday('momentum');
          if (momentumCountToday === 0) {
            const momentumTweets = await generateMomentumPost();
            if (momentumTweets.length > 0) {
              const momentumPosted = await processTweets(momentumTweets);
              totalPosted += momentumPosted;
              if (momentumPosted > 0) {
                console.log(`[rm-worker] Posted momentum tweet!`);
              }
            }
          }
        } catch (err) {
          console.error('[rm-worker] Momentum post failed:', (err as Error).message);
        }
      }

      // ─── ENGAGEMENT (8 AM–11 PM) ─────────────────────────

      if (!POST_ONLY_MODE && hour >= 8 && hour <= 23) {
        try {
          const engagementActions = await runEngagementCycle();
          const mentionActions = await processMentions();
          if (engagementActions + mentionActions > 0) {
            console.log(`[rm-worker] Engagement: ${engagementActions} likes, ${mentionActions} mention replies`);
          }
        } catch (err) {
          console.error('[rm-worker] Engagement cycle failed:', (err as Error).message);
        }
      } else if (POST_ONLY_MODE) {
        console.log('[rm-worker] Post-only mode enabled, skipping engagement');
      }
    }
  } catch (error) {
    console.error('[rm-worker] Cycle error:', error);
  }

  const tweetCount24h = await getTweetCountLast24Hours();
  const engagementCount24h = await getEngagementCountLast24Hours();
  console.log(`[rm-worker] === Cycle end === posted: ${totalPosted}, tweets 24h: ${tweetCount24h}/${MAX_TWEETS_24H}, engagement 24h: ${engagementCount24h}`);
  running = false;
}

// ── Graceful Shutdown ────────────────────────────────────────

function shutdown(signal: string) {
  console.log(`[rm-worker] Received ${signal}, shutting down...`);
  shutdownRequested = true;
  setTimeout(() => process.exit(0), 30000);
}

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

// ── Main Loop ────────────────────────────────────────────────

async function main() {
  console.log('[rm-worker] Rainmaker Twitter Agent V2 started');
  console.log(`[rm-worker] DRY_RUN=${process.env.TWITTER_DRY_RUN || 'false'}`);
  console.log(`[rm-worker] MAX_TWEETS_24H=${MAX_TWEETS_24H}`);
  console.log(`[rm-worker] POST_ONLY_MODE=${POST_ONLY_MODE}`);
  console.log('[rm-worker] Schedule: slate@7, threads@9+13, edu@11, piff@15, cta@17, prime@18, recap@22');
  console.log('[rm-worker] Reactive: live_edge@9-22, momentum@9-22 (auto-trigger)');
  console.log('[rm-worker] Engagement: likes@8-23');
  console.log(`[rm-worker] Interval: ${INTERVAL_MS / 60000} minutes`);

  await runScheduledJobs();

  const interval = setInterval(async () => {
    if (shutdownRequested) {
      clearInterval(interval);
      return;
    }
    await runScheduledJobs();
  }, INTERVAL_MS);
}

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