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

import {
  generateGamePreviews,
  generatePropPicks,
  generateLineAlerts,
  generateHotTakes,
  generateRecaps,
  generateBlogPromos,
  generateRecapThread,
  generateWeeklyRecapThread,
  generateVideoTweet,
  checkEventDrivenAlerts,
  generatePerformanceRecap,
} from './TwitterContentGenerator';
import {
  runEngagementCycle,
  processMentions,
} from './services/twitter-engagement.service';
import {
  postTweet,
  postTweetWithMedia,
  uploadImageSimple,
  uploadMediaChunked,
} from './services/twitter-api.service';
import {
  generateAndDownloadImage,
  generateAndDownloadVideo,
  isMediaAvailable,
} from './services/kie-ai.service';
import {
  insertTweet,
  updateTweetStatus,
  updateTweetPosted,
  getTweetCountLast24Hours,
  hasSlotExecuted,
  insertWorkerRun,
} from './twitter-data-queries';
import { logJob, updateJobStatus } from '../seo/data-queries';
import { orchestrateBreakdown, type BreakdownResult } from './blog-breakdown';
import { updatePickXPost, updateBlogXPostLink, updatePickTweetRow } from './breakdown-data-queries';
import type { TweetContent } from './types';

const INTERVAL_MS = 30 * 60 * 1000; // 30 minutes
const MAX_TWEETS_24H = parseInt(process.env.TWITTER_MAX_TWEETS_PER_24H || '40');
const INTER_TWEET_DELAY_MS = 3000;

let running = false;
let shutdownRequested = false;

// ── 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; // midnight returns 24 with hour12:false
}

function getDayOfWeek(): number {
  const etString = new Date().toLocaleString('en-US', { timeZone: 'America/New_York', weekday: 'long' });
  const days: Record<string, number> = { Sunday: 0, Monday: 1, Tuesday: 2, Wednesday: 3, Thursday: 4, Friday: 5, Saturday: 6 };
  return days[etString] ?? new Date().getDay();
}

/** Returns a Date object representing "now" in Eastern Time.
 *  The underlying JS Date is adjusted so .toISOString().slice(0,10) gives the ET date. */
function getETDate(): Date {
  const etDateStr = new Date().toLocaleDateString('en-CA', { timeZone: 'America/New_York' });
  return new Date(etDateStr + 'T12:00:00'); // noon to avoid DST edge
}

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

async function processTweet(tweet: TweetContent, replyToTweetId?: string): Promise<string | null> {
  const jobId = await logJob({
    agent_type: 'twitter-agent',
    job_type: tweet.contentType,
    status: 'running',
    started_at: new Date(),
  });

  // Blog Breakdown interception — generate blog + rewrite tweet with backlink
  let breakdown: BreakdownResult | null = null;
  if (tweet.isPickType && tweet.enrichedData && tweet.pickDetails) {
    try {
      breakdown = await orchestrateBreakdown(tweet.enrichedData, tweet.pickDetails);
      if (breakdown) {
        tweet.text = breakdown.tweetText;          // replace with URL version
        tweet.blogPostId = breakdown.blogPostId;
        // Skip media for breakdown tweets — let the blog URL card display instead
        tweet.mediaType = null;
        tweet.imagePrompt = undefined;
        console.log(`[worker] Breakdown ready: blog #${breakdown.blogPostId}, pick #${breakdown.pickId}`);
      }
    } catch (err) {
      console.warn('[worker] Breakdown failed, posting without URL:', (err as Error).message);
      // graceful degradation — original tweet text unchanged
    }
  }

  try {
    // 1. Insert to DB
    const rowId = await insertTweet(tweet);

    // Link pick to tweet row if breakdown exists
    if (breakdown?.pickId) {
      await updatePickTweetRow(breakdown.pickId, rowId).catch(() => {});
    }

    // 2. Handle media if requested
    let mediaId: string | undefined;
    if (tweet.mediaType && tweet.imagePrompt && isMediaAvailable()) {
      await updateTweetStatus(rowId, 'media_generating');

      if (tweet.mediaType === 'image') {
        const media = await generateAndDownloadImage(tweet.imagePrompt);
        if (media) {
          mediaId = await uploadImageSimple(media.buffer, media.mimeType);
          await updateTweetStatus(rowId, 'posting', {
            media_url: tweet.imagePrompt,
            twitter_media_id: mediaId,
          });
        }
      } else if (tweet.mediaType === 'video') {
        const media = await generateAndDownloadVideo(tweet.imagePrompt);
        if (media) {
          mediaId = await uploadMediaChunked(media.buffer, media.mimeType, 'tweet_video');
          await updateTweetStatus(rowId, 'posting', {
            media_url: tweet.imagePrompt,
            twitter_media_id: mediaId,
          });
        }
      }
    }

    // 3. Post tweet
    await updateTweetStatus(rowId, 'posting');
    let result;
    if (mediaId) {
      result = await postTweetWithMedia(tweet.text, [mediaId], replyToTweetId || tweet.inReplyTo);
    } else {
      result = await postTweet(tweet.text, replyToTweetId || tweet.inReplyTo);
    }

    // 4. Update DB
    await updateTweetPosted(rowId, result.tweetId);
    await updateJobStatus(jobId, 'completed', { tweetId: result.tweetId });

    // 5. Post-tweet: update pick + blog with X post link
    if (breakdown?.pickId && result.tweetId) {
      const xUrl = `https://x.com/SportsClawai/status/${result.tweetId}`;
      await updatePickXPost(breakdown.pickId, result.tweetId, xUrl).catch(e =>
        console.warn('[worker] Failed to update pick X post:', e.message)
      );
      await updateBlogXPostLink(breakdown.blogPostId, xUrl).catch(e =>
        console.warn('[worker] Failed to update blog X post link:', e.message)
      );
    }

    console.log(`[worker] Posted tweet ${result.tweetId}: "${tweet.text.substring(0, 60)}..."`);
    return result.tweetId;
  } catch (err) {
    const errMsg = (err as Error).message;
    console.error(`[worker] Tweet processing failed:`, errMsg);
    await updateJobStatus(jobId, 'failed', undefined, errMsg);
    return null;
  }
}

async function processTweets(tweets: TweetContent[]): Promise<number> {
  let posted = 0;
  for (const tweet of tweets) {
    // Check rolling 24h limit before each tweet
    const count = await getTweetCountLast24Hours();
    if (count >= MAX_TWEETS_24H) {
      console.log(`[worker] Rolling 24h tweet limit reached (${count}/${MAX_TWEETS_24H}), stopping`);
      break;
    }
    if (shutdownRequested) break;

    const tweetId = await processTweet(tweet);
    if (tweetId) posted++;

    // Delay between tweets
    await new Promise(r => setTimeout(r, INTER_TWEET_DELAY_MS));
  }
  return posted;
}

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

  let posted = 0;
  let parentTweetId: string | null = null;

  for (let i = 0; i < tweets.length; i++) {
    const count = await getTweetCountLast24Hours();
    if (count >= MAX_TWEETS_24H) break;
    if (shutdownRequested) break;

    const tweet = tweets[i];
    const tweetId = await processTweet(tweet, parentTweetId || undefined);

    if (tweetId) {
      if (i === 0) parentTweetId = tweetId; // first tweet = thread parent
      posted++;
    }

    await new Promise(r => setTimeout(r, 3000));
  }

  return posted;
}

// ── Scheduled Slot Handlers ──────────────────────────────────

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

  console.log(`[worker] Executing slot: ${slotName}`);
  try {
    const count = await handler();
    await insertWorkerRun(slotName, count);
    console.log(`[worker] Slot ${slotName} completed — ${count} tweets`);
    return count;
  } catch (err) {
    console.error(`[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('[worker] Previous cycle still running, skipping');
    return;
  }
  running = true;

  const hour = getETHour();
  const day = getDayOfWeek();
  const today = getETDate();
  let totalPosted = 0;

  console.log(`[worker] === Cycle start === ET hour=${hour}, day=${day}, date=${today.toLocaleDateString('en-CA', { timeZone: 'America/New_York' })}`);

  try {
    // ── Event-Driven Alerts (every cycle) ──
    const alerts = await checkEventDrivenAlerts();
    if (alerts.length > 0) {
      const slotKey = `event_driven_${hour}`;
      totalPosted += await executeSlot(slotKey, async () => processTweets(alerts));
    }

    // ── Scheduled Slots ──

    // 6 AM: Game previews
    if (hour === 6) {
      totalPosted += await executeSlot('game_preview_06', async () => {
        const tweets = await generateGamePreviews(today);
        return processTweets(tweets);
      });
    }

    // 7 AM: Prop picks
    if (hour === 7) {
      totalPosted += await executeSlot('prop_pick_07', async () => {
        const tweets = await generatePropPicks(today);
        return processTweets(tweets);
      });
    }

    // 8 AM: Blog promo
    if (hour === 8) {
      totalPosted += await executeSlot('blog_promo_08', async () => {
        const tweets = await generateBlogPromos();
        return processTweets(tweets);
      });
    }

    // 9 AM: Hot take
    if (hour === 9) {
      totalPosted += await executeSlot('hot_take_09', async () => {
        const tweets = await generateHotTakes();
        return processTweets(tweets);
      });
    }

    // Engagement + Mentions: every cycle during active hours (8 AM - 11 PM ET)
    // Runs outside executeSlot so it fires every 30-min cycle, not once per hour
    if (hour >= 8 && hour <= 23) {
      try {
        const engagementActions = await runEngagementCycle();
        const mentionActions = await processMentions();
        if (engagementActions + mentionActions > 0) {
          console.log(`[worker] Engagement: ${engagementActions} likes, ${mentionActions} mention replies`);
        }
      } catch (err) {
        console.error('[worker] Engagement cycle failed:', (err as Error).message);
      }
    }

    // 10 AM: Grade pending picks (before performance recap)
    if (hour === 10) {
      totalPosted += await executeSlot('grading_10', async () => {
        const { gradeAllPendingPicks } = await import('./grading-engine');
        const report = await gradeAllPendingPicks();
        console.log(`[worker] Grading: ${report.graded} graded (${report.wins}W-${report.losses}L-${report.pushes}P, ${report.skipped} skipped)`);
        return 0; // no tweets posted — grading only
      });
    }

    // 11 AM: Line alerts + Performance recap
    if (hour === 11) {
      totalPosted += await executeSlot('line_alert_11', async () => {
        const tweets = await generateLineAlerts();
        return processTweets(tweets);
      });

    }

    // 12 PM: Midday prop picks
    if (hour === 12) {
      totalPosted += await executeSlot('prop_pick_12', async () => {
        const tweets = await generatePropPicks(today);
        return processTweets(tweets);
      });
    }

    // 3 PM: Pre-game line alerts
    if (hour === 15) {
      totalPosted += await executeSlot('line_alert_15', async () => {
        const tweets = await generateLineAlerts();
        return processTweets(tweets);
      });
    }

    // 4 PM: Primetime video
    if (hour === 16) {
      totalPosted += await executeSlot('video_16', async () => {
        const tweet = await generateVideoTweet();
        if (!tweet) return 0;
        return processTweets([tweet]);
      });
    }

    // 9 PM: Recaps
    if (hour === 21) {
      totalPosted += await executeSlot('recap_21', async () => {
        const tweets = await generateRecaps();
        return processTweets(tweets);
      });
    }

    // 10 PM: Daily performance recap (W/L record)
    if (hour === 22) {
      totalPosted += await executeSlot('performance_recap_22', async () => {
        const tweet = await generatePerformanceRecap();
        if (!tweet) return 0;
        return processTweets([tweet]);
      });
    }

    // 10 PM: Recap thread
    if (hour === 22) {
      totalPosted += await executeSlot('recap_thread_22', async () => {
        const tweets = await generateRecapThread();
        return processThread(tweets);
      });
    }

    // Monday 8 AM: Weekly recap thread
    if (day === 1 && hour === 8) {
      totalPosted += await executeSlot('weekly_recap', async () => {
        const tweets = await generateWeeklyRecapThread();
        return processThread(tweets);
      });
    }
  } catch (error) {
    console.error('[worker] Cycle error:', error);
  }

  const tweetCount24h = await getTweetCountLast24Hours();
  console.log(`[worker] === Cycle end === posted this cycle: ${totalPosted}, 24h total: ${tweetCount24h}/${MAX_TWEETS_24H}`);
  running = false;
}

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

function shutdown(signal: string) {
  console.log(`[worker] Received ${signal}, shutting down gracefully...`);
  shutdownRequested = true;
  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 Twitter Agent started');
  console.log(`[worker] DRY_RUN=${process.env.TWITTER_DRY_RUN || 'false'}`);
  console.log(`[worker] MEDIA_ENABLED=${process.env.TWITTER_MEDIA_ENABLED || 'true'}`);
  console.log(`[worker] ENGAGEMENT_ENABLED=${process.env.TWITTER_ENGAGEMENT_ENABLED || 'false'}`);
  console.log(`[worker] MAX_TWEETS_24H=${MAX_TWEETS_24H}`);
  console.log('[worker] Schedule: previews@6, props@7+12, blog@8, hot-take@9, grading@10, engagement@8-23(every cycle), lines@11+15, video@16, recaps@21+22, weekly@Mon8');
  console.log(`[worker] Interval: ${INTERVAL_MS / 60000} minutes`);

  // Run immediately on start
  await runScheduledJobs();

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

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