// backend/src/jobs/pipeline-worker.js
import { Worker, Queue, QueueEvents } from 'bullmq';
import { query } from '../utils/db.js';
import { getProviderForUser } from '../services/ai-provider.js';
import { logger } from '../utils/logger.js';
import { format } from 'date-fns';
import {
  IntelAgent, TopicGeneratorAgent, ScriptWriterAgent,
  XPostAgent, ThumbnailAgent, BlogAgent, NewsletterAgent,
  OutreachAgent, ClipFactoryAgent, PerformanceEvalAgent
} from './agents.js';

const connection = { host: 'localhost', port: 6379 };
if (process.env.REDIS_URL) {
  const url = new URL(process.env.REDIS_URL);
  connection.host = url.hostname;
  connection.port = parseInt(url.port) || 6379;
}

export const pipelineQueue = new Queue('pipeline', { connection });

export function startPipelineWorker() {
  const worker = new Worker('pipeline', async (job) => {
    const { userId, triggeredBy, approvedTopics, nicheId } = job.data;

    logger.info(`Pipeline started for user ${userId}`, { triggeredBy, jobId: job.id, topicsProvided: approvedTopics?.length || 0 });

    // Get user info + plan
    const { rows: userRows } = await query(
      `SELECT u.*, p.scripts_per_day, p.posts_per_day, p.byok
       FROM users u LEFT JOIN plans p ON u.plan_id = p.id WHERE u.id = $1`,
      [userId]
    );
    if (!userRows[0]) throw new Error('User not found');
    const user = userRows[0];

    // Check user is active
    if (!['active', 'trial'].includes(user.status)) {
      throw new Error('User subscription not active');
    }

    // Get user profile
    const { rows: profileRows } = await query('SELECT * FROM user_profiles WHERE user_id = $1', [userId]);
    const profile = profileRows[0];
    if (!profile?.niche) throw new Error('User profile not configured (niche required)');

    // Create pipeline run record
    const { rows: runRows } = await query(
      `INSERT INTO pipeline_runs (user_id, status, triggered_by, niche_id) VALUES ($1, 'running', $2, $3) RETURNING id`,
      [userId, triggeredBy, nicheId || null]
    );
    const runId = runRows[0].id;
    await query('UPDATE pipeline_runs SET started_at = NOW() WHERE id = $1', [runId]);

    const config = {
      niche: profile.niche,
      brand: profile.brand_name,
      targetAudience: profile.target_audience,
      agents: {
        scriptsPerDay: profile.scripts_per_day || user.scripts_per_day || 3,
        postsPerDay: profile.posts_per_day || user.posts_per_day || 3,
      }
    };

    const voiceProfile = {
      tone: profile.tone,
      writingStyle: profile.writing_style,
      personality: profile.personality,
      avoidWords: profile.avoid_words || [],
      cta: profile.cta,
      examplePhrases: profile.example_phrases || [],
    };

    try {
      const ai = await getProviderForUser(userId, { byok: user.byok });
      const results = {};
      const agentsRun = [];

      const saveContent = async (type, title, body, metadata = {}) => {
        await query(
          `INSERT INTO generated_content (user_id, run_id, content_type, title, body, metadata)
           VALUES ($1, $2, $3, $4, $5, $6)`,
          [userId, runId, type, title, body, JSON.stringify(metadata)]
        );
      };

      // ── 1. INTEL ──────────────────────────────────────
      await job.updateProgress(10);
      const intel = new IntelAgent(ai, config, voiceProfile);
      results.intel = await intel.run();
      await saveContent('intel', `Intel Report ${format(new Date(), 'yyyy-MM-dd')}`, results.intel);
      agentsRun.push('intel');

      // ── 2. TOPICS — use client-approved topics if provided ──
      await job.updateProgress(20);
      if (approvedTopics && approvedTopics.length > 0) {
        // Client pre-approved these topics via the Topic Approval flow
        results.topics = approvedTopics.map(t => t.title);
        results.topicIds = approvedTopics.map(t => t.id);
        logger.info(`Using ${results.topics.length} client-approved topics`);
      } else {
        // Fallback: auto-generate from intel (scheduled runs without approval)
        const topicAgent = new TopicGeneratorAgent(ai, config, voiceProfile);
        const topicResult = await topicAgent.run(results.intel, config.agents.scriptsPerDay);
        results.topics = topicResult.topics.length > 0 ? topicResult.topics : [config.niche + ' deep dive', 'Common mistakes in ' + config.niche, 'Beginner guide to ' + config.niche];
      }
      agentsRun.push('topics');

      // ── 3. SCRIPTS ────────────────────────────────────
      await job.updateProgress(35);
      const scriptAgent = new ScriptWriterAgent(ai, config, voiceProfile);
      results.scripts = [];
      for (let i = 0; i < results.topics.length; i++) {
        const script = await scriptAgent.run(results.topics[i], results.intel);
        results.scripts.push({ topic: results.topics[i], script });
        await saveContent('script', results.topics[i], script, { topicIndex: i });
        await job.updateProgress(35 + (i + 1) * 8);
      }
      agentsRun.push('scripts');

      // ── 4. X POSTS ────────────────────────────────────
      await job.updateProgress(65);
      const xAgent = new XPostAgent(ai, config, voiceProfile);
      results.posts = await xAgent.run(results.intel, results.scripts);
      await saveContent('post', `X Posts ${format(new Date(), 'yyyy-MM-dd')}`, results.posts);
      agentsRun.push('xposts');

      // ── 5. THUMBNAILS ─────────────────────────────────
      await job.updateProgress(70);
      const thumbAgent = new ThumbnailAgent(ai, config, voiceProfile);
      results.thumbnails = await thumbAgent.run(results.scripts);
      await saveContent('thumbnail', `Thumbnails ${format(new Date(), 'yyyy-MM-dd')}`, results.thumbnails);
      agentsRun.push('thumbnails');

      // ── 5b. THUMBNAIL IMAGES (Codex, if user has ChatGPT connected) ──
      try {
        const { getConnectionStatus, generate: codexGenerate } = await import('../services/codex-image.js');
        const { enqueueImageGeneration } = await import('./codex-image-worker.js');
        const codexStatus = await getConnectionStatus(userId);

        if (codexStatus.connected) {
          // Extract image prompts from thumbnail text
          const promptMatches = results.thumbnails.match(/AI image prompt:\s*([^\n]+)/gi) || [];
          const imagePrompts = promptMatches.slice(0, 3).map(m =>
            m.replace(/AI image prompt:\s*/i, '').trim()
          );

          for (let i = 0; i < imagePrompts.length; i++) {
            const imgPrompt = imagePrompts[i];
            if (!imgPrompt || imgPrompt.length < 5) continue;
            const fn = `thumbnail_${runId.slice(0, 8)}_${i + 1}.png`;
            try {
              await enqueueImageGeneration(userId, {
                prompt:          imgPrompt,
                fileName:        fn,
                relatedScriptId: results.scriptIds?.[i] || null,
              });
              logger.info(`Queued Codex thumbnail image ${i + 1} for user ${userId}`);
            } catch (imgErr) {
              logger.warn(`Failed to queue thumbnail image ${i + 1}`, { error: imgErr.message });
            }
          }
          agentsRun.push('thumbnail_images');
        }
      } catch (codexErr) {
        // Never fail the pipeline if image gen is unavailable
        logger.warn('Codex thumbnail integration skipped', { error: codexErr.message });
      }

      // ── 6. BLOG ───────────────────────────────────────
      await job.updateProgress(75);
      const blogAgent = new BlogAgent(ai, config, voiceProfile);
      results.blog = await blogAgent.run(results.scripts[0] || { topic: results.topics[0], script: '' });
      await saveContent('blog', results.topics[0], results.blog);
      agentsRun.push('blog');

      // ── 7. NEWSLETTER ─────────────────────────────────
      await job.updateProgress(82);
      const newsletterAgent = new NewsletterAgent(ai, config, voiceProfile);
      results.newsletter = await newsletterAgent.run(results.intel, results.scripts);
      await saveContent('newsletter', `Newsletter ${format(new Date(), 'yyyy-MM-dd')}`, results.newsletter);
      agentsRun.push('newsletter');

      // ── 8. OUTREACH ───────────────────────────────────
      await job.updateProgress(88);
      const outreachAgent = new OutreachAgent(ai, config, voiceProfile);
      results.outreach = await outreachAgent.run();
      await saveContent('outreach', `Outreach ${format(new Date(), 'yyyy-MM-dd')}`, results.outreach);
      agentsRun.push('outreach');

      // ── 9. CLIPS ──────────────────────────────────────
      await job.updateProgress(94);
      const clipAgent = new ClipFactoryAgent(ai, config, voiceProfile);
      results.clips = await clipAgent.run(results.scripts);
      await saveContent('clip', `Clip Ideas ${format(new Date(), 'yyyy-MM-dd')}`, results.clips);
      agentsRun.push('clips');

      await job.updateProgress(100);

      // Mark run as completed
      await query(
        `UPDATE pipeline_runs SET status = 'completed', completed_at = NOW(), agents_run = $1, scripts_count = $2, provider_used = $3 WHERE id = $4`,
        [agentsRun, results.scripts.length, ai.provider, runId]
      );

      // Mark approved topics as used in this run
      if (results.topicIds?.length > 0) {
        await query(
          'UPDATE topic_proposals SET used_in_run_id = $1 WHERE id = ANY($2)',
          [runId, results.topicIds]
        );
      }

      logger.info(`Pipeline completed for user ${userId}`, { runId, scriptsCount: results.scripts.length });
      return { runId, scriptsCount: results.scripts.length, agentsRun };

    } catch (err) {
      await query(
        `UPDATE pipeline_runs SET status = 'failed', completed_at = NOW(), error_message = $1 WHERE id = $2`,
        [err.message, runId]
      );
      throw err;
    }
  }, {
    connection,
    concurrency: 3, // Max 3 pipelines simultaneously
    limiter: { max: 10, duration: 60000 }, // 10 jobs per minute global
  });

  worker.on('failed', (job, err) => logger.error(`Pipeline job ${job.id} failed`, err.message));
  worker.on('completed', (job, result) => logger.info(`Pipeline job ${job.id} completed`, result));

  logger.info('Pipeline worker started');
  return worker;
}

export async function enqueuePipeline(userId, triggeredBy = 'manual', options = {}) {
  const job = await pipelineQueue.add(
    'run-pipeline',
    { userId, triggeredBy, ...options },
    {
      attempts: 2,
      backoff: { type: 'exponential', delay: 5000 },
      removeOnComplete: { count: 50 },
      removeOnFail: { count: 20 },
    }
  );
  return job.id;
}
