/**
 * Sales Script AI Context Routes
 *
 * API endpoints for Sales Script AI generation.
 * POST /auto-fill — generate multiple sales scripts from company context
 * POST /regenerate — regenerate a single sales script using existing context
 */

import express, { Request, Response } from 'express';
import { body, validationResult } from 'express-validator';
import { authenticate } from '../middleware/auth';
import { requirePermission } from '../middleware/permissions';
import { SalesScriptPipeline } from '../services/aiContext/salesScriptPipeline';
import { SalesScriptPipelineInputs, buildSingleSalesScriptPrompt } from '../services/aiContext/salesScriptPrompts';
import { aiContextService, computeSalesScriptAutoFillMapping } from '../services/aiContext/aiContextService';
import { generateWithAI } from '../utils/aiProvider';
import { parseJsonFromAI } from '../services/aiContext/parseJsonFromAI';
import { getModels } from '../models';
import { createJob, updateJobProgress, completeJob, failJob, getJob, addJobCompletedScript } from '../services/aiContext/aiJobManager';
import { isDuplicateOfAny, getSimilarityThreshold } from '../services/aiContext/salesScriptPipeline';

const router = express.Router();
router.use(authenticate);

router.get(
  '/status/:jobId',
  async (req: Request, res: Response) => {
    const { jobId } = req.params;
    const job = getJob(jobId);

    if (!job) {
      res.status(404).json({ error: 'Job not found' });
      return;
    }

    res.json({
      jobId: job.jobId,
      status: job.status,
      progress: job.progress,
      step: job.step,
      result: job.result,
      error: job.error,
    });
  }
);

// ============================================
// POST /auto-fill
// ============================================

router.post(
  '/auto-fill',
  requirePermission('sales-scripts', 'ai-generate'),
  [
    body('companyId').notEmpty().withMessage('Company ID is required'),
  ],
  async (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ error: 'Validation failed', details: errors.array() });
      return;
    }

    const { companyId, targetCount, customInstructions } = req.body;
    const count = Math.min(Math.max(Number(targetCount) || 10, 1), 10);

    try {
      const job = createJob('sales-script', companyId, req.body._moduleId);
      res.status(202).json({ jobId: job.jobId, status: 'processing' });

      setImmediate(async () => {
        try {
          const { Company, BusinessProfile, ICP, Product } = getModels();
          const company = await Company.findById(companyId);
          if (!company) {
            failJob(job.jobId, 'Company not found');
            return;
          }

          let businessProfile: any = null;
          try { businessProfile = await BusinessProfile.findOne({ companyId }); } catch {}

          const companyContexts = await aiContextService.getByCompany(companyId, 'company-creation');
          const latestCompanyContext = companyContexts.find((c: any) => {
            const a = c.analysis?.toObject?.() || c.analysis || {};
            return c.status === 'approved' || a.industryType || a.businessModel || a.businessSummary;
          });

          let icpData: any = null;
          try { icpData = await ICP.findOne({ companyId, isActive: true }).sort({ createdAt: -1 }); } catch {}

          let brandStrategyData: any = null;
          try {
            const { ModuleData } = getModels();
            const brandStrategyDoc = await ModuleData.findOne({ moduleId: 'brand-strategy', companyId });
            if (brandStrategyDoc?.data) {
              brandStrategyData = brandStrategyDoc.data;
            }
          } catch {}

          let productNames: string[] | undefined;
          let productDescriptions: string[] | undefined;
          try {
            const products = await Product.find({ companyId }).limit(10);
            if (products.length > 0) {
              productNames = products.map((p: any) => p.name);
              productDescriptions = products.map((p: any) => p.description || p.shortDescription || '');
            }
          } catch {}

          const pipelineInputs: SalesScriptPipelineInputs = {
            // The user's own brief from the "Generate with AI" popup or the AI Chat
            // generation flow. Stated as the highest-priority instruction in the
            // prompt, so it wins over the derived company context.
            customInstructions:
              typeof customInstructions === 'string' && customInstructions.trim()
                ? customInstructions.trim()
                : undefined,
            companyName: company.name,
            companyDescription: company.description || businessProfile?.description || undefined,
            companyIndustry: businessProfile?.primaryIndustry || undefined,
            companyBusinessModel: businessProfile?.businessModel || undefined,
            companyTargetAudience: undefined,
            companyPrimaryOffering: undefined,
            companyUsps: undefined,
            companyTargetGeography: businessProfile?.targetGeography || undefined,
            companyCountry: businessProfile?.country || undefined,
            targetCount: count,
          };

          if (latestCompanyContext) {
            const analysis = (latestCompanyContext as any).analysis?.toObject?.() || (latestCompanyContext as any).analysis || {};
            if (analysis.industryType && !pipelineInputs.companyIndustry) pipelineInputs.companyIndustry = analysis.industryType;
            if (analysis.businessModel && !pipelineInputs.companyBusinessModel) pipelineInputs.companyBusinessModel = analysis.businessModel;
            if (analysis.targetAudience?.primary) pipelineInputs.companyTargetAudience = analysis.targetAudience.primary;
            if (analysis.primaryOffering) pipelineInputs.companyPrimaryOffering = analysis.primaryOffering;
            if (analysis.uspSuggestions?.length) pipelineInputs.companyUsps = analysis.uspSuggestions;
            if (!pipelineInputs.companyTargetGeography && analysis.targetGeography) pipelineInputs.companyTargetGeography = analysis.targetGeography;
            if (!pipelineInputs.companyCountry && analysis.country) pipelineInputs.companyCountry = analysis.country;
          }

          if (icpData) {
            pipelineInputs.icpName = icpData.name || undefined;
            pipelineInputs.icpIndustry = icpData.industry || undefined;
            pipelineInputs.icpCompanySize = icpData.companySize || undefined;
            pipelineInputs.icpPainPoints = icpData.painPoints || undefined;
            pipelineInputs.icpBusinessGoals = icpData.businessGoals || undefined;
            if (icpData.challenges?.length) pipelineInputs.icpChallenges = icpData.challenges;
          }

          if (brandStrategyData) {
            pipelineInputs.brandArchetype = brandStrategyData.brandArchetype || undefined;
            pipelineInputs.brandPersonality = brandStrategyData.brandPersonality || undefined;
            pipelineInputs.brandValues = brandStrategyData.brandValues || undefined;
            pipelineInputs.brandPositioning = brandStrategyData.brandPositioning || undefined;
            pipelineInputs.brandVoice = brandStrategyData.brandVoice || undefined;
          }

          if (productNames?.length) {
            pipelineInputs.productNames = productNames;
            pipelineInputs.productDescriptions = productDescriptions;
          }

          updateJobProgress(job.jobId, 10, 'Preparing context...');
          const pipeline = new SalesScriptPipeline(pipelineInputs, (progress, step) => updateJobProgress(job.jobId, progress, step), 'ollama');
          const result = await pipeline.run();

          // Store full analysis in AiContext
          try {
            const context = await aiContextService.create({
              companyId,
              moduleSource: 'sales-script',
              analysisType: 'full-analysis',
              inputs: { companyName: company.name, description: pipelineInputs.companyDescription },
              analysis: { scripts: result.scripts },
              metadata: {
                pipelineVersion: result.pipelineVersion,
                provider: result.provider,
                model: result.aiModel,
                tokensUsed: result.tokensUsed,
                inputTokens: result.inputTokens,
                outputTokens: result.outputTokens,
                processingTimeMs: result.processingTimeMs,
                latencyMs: result.latencyMs,
                overallConfidence: result.overallConfidence,
                fieldConfidences: {},
                finishReason: result.finishReason,
                apiKeyMasked: result.apiKeyMasked,
              },
            });
            await aiContextService.updateStatus(context.id, 'approved');
          } catch (ctxErr: any) {
            console.warn(`[SalesScript-AutoFill] AiContext save failed (non-fatal): ${ctxErr.message}`);
          }

          // Map each script through the auto-fill mapping
          const autoFillDataArray = result.scripts
            .map((sAnalysis) => computeSalesScriptAutoFillMapping(sAnalysis))
            .filter((data) => data.title);

          // Build status breakdown for the job result
          const statusBreakdown: Record<string, number> = {};
          for (const data of autoFillDataArray) {
            const s = data.status || 'draft';
            statusBreakdown[s] = (statusBreakdown[s] || 0) + 1;
          }

          completeJob(job.jobId, { scripts: autoFillDataArray, statusBreakdown, aiModel: result.aiModel, provider: result.provider }, 'generated');
          console.log(`[SalesScript-AutoFill] Job ${job.jobId} completed. ${autoFillDataArray.length} scripts generated.`);
        } catch (err: any) {
          console.error(`[SalesScript-AutoFill] Job ${job.jobId} failed:`, err.message);
          failJob(job.jobId, err.message || 'AI generation failed');
        }
      });
    } catch (error: any) {
      console.error('[SalesScript-AutoFill] Error:', error.message);
      res.status(500).json({ error: 'Failed to generate sales scripts', details: error.message });
    }
  }
);

// ============================================
// POST /regenerate (single script)
// ============================================

router.post(
  '/regenerate',
  requirePermission('sales-scripts', 'ai-generate'),
  [
    body('companyId').notEmpty().withMessage('Company ID is required'),
  ],
  async (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ error: 'Validation failed', details: errors.array() });
      return;
    }

    const { companyId } = req.body;

    const job = createJob('sales-script', companyId, req.body._moduleId);
    res.status(202).json({ jobId: job.jobId, status: 'processing' });

    setImmediate(async () => {
      try {
        const { Company, BusinessProfile, ICP, Product } = getModels();
        const company = await Company.findById(companyId);
        if (!company) {
          failJob(job.jobId, 'Company not found');
          return;
        }

        let businessProfile: any = null;
        try { businessProfile = await BusinessProfile.findOne({ companyId }); } catch {}

        const companyContexts = await aiContextService.getByCompany(companyId, 'company-creation');
        const latestCompanyContext = companyContexts.find((c: any) => {
          const a = c.analysis?.toObject?.() || c.analysis || {};
          return c.status === 'approved' || a.industryType || a.businessModel || a.businessSummary;
        });

        let icpData: any = null;
        try { icpData = await ICP.findOne({ companyId, isActive: true }).sort({ createdAt: -1 }); } catch {}

        let brandStrategyData: any = null;
        try {
          const { ModuleData } = getModels();
          const brandStrategyDoc = await ModuleData.findOne({ moduleId: 'brand-strategy', companyId });
          if (brandStrategyDoc?.data) {
            brandStrategyData = brandStrategyDoc.data;
          }
        } catch {}

        let productNames: string[] | undefined;
        let productDescriptions: string[] | undefined;
        try {
          const products = await Product.find({ companyId }).limit(10);
          if (products.length > 0) {
            productNames = products.map((p: any) => p.name);
            productDescriptions = products.map((p: any) => p.description || p.shortDescription || '');
          }
        } catch {}

        const pipelineInputs: SalesScriptPipelineInputs = {
          companyName: company.name,
          companyDescription: company.description || businessProfile?.description || undefined,
          companyIndustry: businessProfile?.primaryIndustry || undefined,
          companyBusinessModel: businessProfile?.businessModel || undefined,
          companyTargetGeography: businessProfile?.targetGeography || undefined,
          companyCountry: businessProfile?.country || undefined,
          targetCount: 1,
        };

        if (latestCompanyContext) {
          const analysis = (latestCompanyContext as any).analysis?.toObject?.() || (latestCompanyContext as any).analysis || {};
          if (analysis.industryType && !pipelineInputs.companyIndustry) pipelineInputs.companyIndustry = analysis.industryType;
          if (analysis.businessModel && !pipelineInputs.companyBusinessModel) pipelineInputs.companyBusinessModel = analysis.businessModel;
          if (analysis.targetAudience?.primary) pipelineInputs.companyTargetAudience = analysis.targetAudience.primary;
          if (analysis.primaryOffering) pipelineInputs.companyPrimaryOffering = analysis.primaryOffering;
          if (analysis.uspSuggestions?.length) pipelineInputs.companyUsps = analysis.uspSuggestions;
          if (!pipelineInputs.companyTargetGeography && analysis.targetGeography) pipelineInputs.companyTargetGeography = analysis.targetGeography;
          if (!pipelineInputs.companyCountry && analysis.country) pipelineInputs.companyCountry = analysis.country;
        }

        if (icpData) {
          pipelineInputs.icpName = icpData.name || undefined;
          pipelineInputs.icpIndustry = icpData.industry || undefined;
          pipelineInputs.icpCompanySize = icpData.companySize || undefined;
          pipelineInputs.icpPainPoints = icpData.painPoints || undefined;
          pipelineInputs.icpBusinessGoals = icpData.businessGoals || undefined;
          if (icpData.challenges?.length) pipelineInputs.icpChallenges = icpData.challenges;
        }

        if (brandStrategyData) {
          pipelineInputs.brandArchetype = brandStrategyData.brandArchetype || undefined;
          pipelineInputs.brandPersonality = brandStrategyData.brandPersonality || undefined;
          pipelineInputs.brandValues = brandStrategyData.brandValues || undefined;
          pipelineInputs.brandPositioning = brandStrategyData.brandPositioning || undefined;
          pipelineInputs.brandVoice = brandStrategyData.brandVoice || undefined;
        }

        if (productNames?.length) {
          pipelineInputs.productNames = productNames;
          pipelineInputs.productDescriptions = productDescriptions;
        }

        updateJobProgress(job.jobId, 10, 'Preparing context...');
        const pipeline = new SalesScriptPipeline(pipelineInputs, (progress, step) => updateJobProgress(job.jobId, progress, step), 'ollama');
        const result = await pipeline.run();

        const autoFillDataArray = result.scripts
          .map((sAnalysis) => computeSalesScriptAutoFillMapping(sAnalysis))
          .filter((data) => data.title);

        const statusBreakdown: Record<string, number> = {};
        for (const data of autoFillDataArray) {
          const s = data.status || 'draft';
          statusBreakdown[s] = (statusBreakdown[s] || 0) + 1;
        }

        completeJob(job.jobId, { scripts: autoFillDataArray, statusBreakdown, aiModel: result.aiModel, provider: result.provider }, 'regenerated');
        console.log(`[SalesScript-Regenerate] Job ${job.jobId} completed.`);
      } catch (err: any) {
        console.error(`[SalesScript-Regenerate] Job ${job.jobId} failed:`, err.message);
        failJob(job.jobId, err.message || 'AI regeneration failed');
      }
    });
  }
);

// ============================================
// POST /auto-fill-streaming (one-by-one, single-call generation)
// ============================================

router.post(
  '/auto-fill-streaming',
  requirePermission('sales-scripts', 'ai-generate'),
  [
    body('companyId').notEmpty().withMessage('Company ID is required'),
  ],
  async (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ error: 'Validation failed', details: errors.array() });
      return;
    }

    const { companyId, language, customInstructions, linkedData } = req.body;

    // Debug: Log incoming request parameters
    console.log('═══════════════════════════════════════════════════════════════');
    console.log('[SalesScript-Streaming] 📥 REQUEST RECEIVED');
    console.log('[SalesScript-Streaming] 📋 Parameters:');
    console.log('  └─ companyId:', companyId);
    console.log('  └─ language:', language || 'English (default)');
    console.log('  └─ customInstructions:', customInstructions ? `"${customInstructions.substring(0, 100)}${customInstructions.length > 100 ? '...' : ''}"` : '(none)');
    console.log('[SalesScript-Streaming] 📦 Linked Data:');
    if (linkedData && Object.keys(linkedData).length > 0) {
      for (const [key, value] of Object.entries(linkedData as Record<string, any>)) {
        if (Array.isArray(value)) {
          console.log(`  └─ ${key}: [${value.length} items]`, value.slice(0, 3));
        } else if (value) {
          console.log(`  └─ ${key}:`, value);
        }
      }
    } else {
      console.log('  └─ (no linked data)');
    }
    console.log('═══════════════════════════════════════════════════════════════');

    // 5 by default; the AI Chat generation flow sends the quantity the user
    // picked, so honour it (clamped) rather than always producing 5.
    const TARGET_COUNT = Math.min(Math.max(Number(req.body.targetCount) || 5, 1), 10);
    const MAX_EXTRA_RETRIES = 5;
    const MAX_TOTAL_TIME_MS = 1500000; // 25 minutes max for entire job (not per script)
    const jobStartTime = Date.now();

    const job = createJob('sales-script', companyId, req.body._moduleId);
    res.status(202).json({ jobId: job.jobId, status: 'processing' });

    setImmediate(async () => {
      try {
        // Check if we've exceeded total time budget
        if (Date.now() - jobStartTime > MAX_TOTAL_TIME_MS) {
          console.error(`[SalesScript-Streaming] Job ${job.jobId} exceeded max time budget of ${MAX_TOTAL_TIME_MS}ms`);
          failJob(job.jobId, `Generation timeout after ${Math.round(MAX_TOTAL_TIME_MS / 60000)} minutes`);
          return;
        }
        const { Company, BusinessProfile, ICP, Product, SalesScript } = getModels();
        const company = await Company.findById(companyId);
        if (!company) {
          failJob(job.jobId, 'Company not found');
          return;
        }

        // Load context
        let businessProfile: any = null;
        try { businessProfile = await BusinessProfile.findOne({ companyId }); } catch {}

        const companyContexts = await aiContextService.getByCompany(companyId, 'company-creation');
        const latestCompanyContext = companyContexts.find((c: any) => {
          const a = c.analysis?.toObject?.() || c.analysis || {};
          return c.status === 'approved' || a.industryType || a.businessModel || a.businessSummary;
        });

        let icpData: any = null;
        try { icpData = await ICP.findOne({ companyId, isActive: true }).sort({ createdAt: -1 }); } catch {}

        let brandStrategyData: any = null;
        try {
          const { ModuleData } = getModels();
          const brandStrategyDoc = await ModuleData.findOne({ moduleId: 'brand-strategy', companyId });
          if (brandStrategyDoc?.data) {
            brandStrategyData = brandStrategyDoc.data;
          }
        } catch {}

        let productNames: string[] | undefined;
        let productDescriptions: string[] | undefined;
        try {
          const products = await Product.find({ companyId }).limit(10);
          if (products.length > 0) {
            productNames = products.map((p: any) => p.name);
            productDescriptions = products.map((p: any) => p.description || p.shortDescription || '');
          }
        } catch {}

        // Build pipeline inputs (reused for each script)
        const baseInputs: SalesScriptPipelineInputs = {
          companyName: company.name,
          companyDescription: company.description || businessProfile?.description || undefined,
          companyIndustry: businessProfile?.primaryIndustry || undefined,
          companyBusinessModel: businessProfile?.businessModel || undefined,
          companyTargetAudience: undefined,
          companyPrimaryOffering: undefined,
          companyUsps: undefined,
          companyTargetGeography: businessProfile?.targetGeography || undefined,
          companyCountry: businessProfile?.country || undefined,
          targetCount: 1,
          language: language || 'English', // Default to English if not specified
          customInstructions: customInstructions || undefined, // Pass through user's custom instructions
        };

        console.log('[SalesScript-Streaming] 📝 Base inputs built:');
        console.log('  └─ companyName:', baseInputs.companyName);
        console.log('  └─ language:', baseInputs.language);
        console.log('  └─ customInstructions:', baseInputs.customInstructions ? 'provided' : 'none');

        if (latestCompanyContext) {
          const analysis = (latestCompanyContext as any).analysis?.toObject?.() || (latestCompanyContext as any).analysis || {};
          if (analysis.industryType && !baseInputs.companyIndustry) baseInputs.companyIndustry = analysis.industryType;
          if (analysis.businessModel && !baseInputs.companyBusinessModel) baseInputs.companyBusinessModel = analysis.businessModel;
          if (analysis.targetAudience?.primary) baseInputs.companyTargetAudience = analysis.targetAudience.primary;
          if (analysis.primaryOffering) baseInputs.companyPrimaryOffering = analysis.primaryOffering;
          if (analysis.uspSuggestions?.length) baseInputs.companyUsps = analysis.uspSuggestions;
          if (!baseInputs.companyTargetGeography && analysis.targetGeography) baseInputs.companyTargetGeography = analysis.targetGeography;
          if (!baseInputs.companyCountry && analysis.country) baseInputs.companyCountry = analysis.country;
        }

        if (icpData) {
          baseInputs.icpName = icpData.name || undefined;
          baseInputs.icpIndustry = icpData.industry || undefined;
          baseInputs.icpCompanySize = icpData.companySize || undefined;
          baseInputs.icpPainPoints = icpData.painPoints || undefined;
          baseInputs.icpBusinessGoals = icpData.businessGoals || undefined;
          if (icpData.challenges?.length) baseInputs.icpChallenges = icpData.challenges;
        }

        if (brandStrategyData) {
          baseInputs.brandArchetype = brandStrategyData.brandArchetype || undefined;
          baseInputs.brandPersonality = brandStrategyData.brandPersonality || undefined;
          baseInputs.brandValues = brandStrategyData.brandValues || undefined;
          baseInputs.brandPositioning = brandStrategyData.brandPositioning || undefined;
          baseInputs.brandVoice = brandStrategyData.brandVoice || undefined;
        }

        if (productNames?.length) {
          baseInputs.productNames = productNames;
          baseInputs.productDescriptions = productDescriptions;
        }

        // Fetch existing scripts from DB for cross-batch dedup
        let existingDbScripts: any[] = [];
        try {
          existingDbScripts = await SalesScript.find({ companyId }).lean();
        } catch (err: any) {
          console.warn(`[SalesScript-Streaming] Failed to fetch existing scripts for dedup: ${err.message}`);
        }

        const existingFieldsForDedup = existingDbScripts.map((s: any) => ({
          openingLine: s.openingLine || '',
          hook: s.hook || '',
          valueProposition: s.valueProposition || '',
        }));

        // Generate scripts one by one using single-call prompt (1 AI call per script, not 3-4)
        const generatedInBatch: Record<string, any>[] = [];
        const savedScriptIds: string[] = [];
        const statusBreakdown: Record<string, number> = {};
        let lastProvider = 'unknown';
        let lastAiModel = 'unknown';
        let successCount = 0;
        let totalAttempts = 0;
        const maxTotalAttempts = TARGET_COUNT + MAX_EXTRA_RETRIES;

        while (successCount < TARGET_COUNT && totalAttempts < maxTotalAttempts) {
          // Check if we've exceeded total time budget
          if (Date.now() - jobStartTime > MAX_TOTAL_TIME_MS) {
            console.error(`[SalesScript-Streaming] Job ${job.jobId} exceeded max time budget after ${successCount} scripts generated`);
            failJob(job.jobId, `Generation timeout after ${successCount} scripts. Max time: ${Math.round(MAX_TOTAL_TIME_MS / 60000)} minutes`);
            return;
          }

          totalAttempts++;
          updateJobProgress(job.jobId, 5 + Math.round((successCount / TARGET_COUNT) * 90), `Generating script ${successCount + 1}/${TARGET_COUNT}...`);

          // Pre-compute language info for use in try/catch
          const currentLanguage = baseInputs.language || 'English';
          const isNonEnglish = currentLanguage.toLowerCase() !== 'english';

          // Per-call AI timeout (limits each individual API call within generateWithAI,
          // which may retry internally up to MAX_RETRIES times and fall through providers).
          // Per-attempt wall-clock timeout caps the TOTAL time for one script attempt
          // including all internal retries and provider fallbacks — without this, a
          // single attempt can compound to 15+ minutes (3 retries × 5 min × N providers)
          // and exhaust the 25-minute job budget after just 2 scripts.
          const TIMEOUT_PER_CALL = isNonEnglish ? 180000 : 120000; // 3 min non-English, 2 min English
          const WALL_CLOCK_PER_ATTEMPT = isNonEnglish ? 420000 : 270000; // 7 min non-English, 4.5 min English

          // Declare variables outside try block so they're accessible in catch
          let prompt: ReturnType<typeof buildSingleSalesScriptPrompt> | undefined;
          let attemptStartTime: number = Date.now();

          try {
            // Single AI call per script — much faster than 3-stage pipeline
            prompt = buildSingleSalesScriptPrompt(baseInputs, successCount, existingFieldsForDedup);
            const userId = req.user?._id?.toString() || req.user?.id;
            attemptStartTime = Date.now();
            console.log(`[SalesScript-Streaming] Attempt ${totalAttempts}: Starting AI generation (callTimeout: ${TIMEOUT_PER_CALL}ms, wallClock: ${WALL_CLOCK_PER_ATTEMPT}ms, language: ${currentLanguage})...`);

            // Race the AI generation against a wall-clock timeout so internal
            // retries/fallbacks in generateWithAI cannot consume the entire job budget.
            // Without this cap, a single attempt compounds to 15+ min (3 retries ×
            // 5 min call timeout × N providers), exhausting the 25-min budget after
            // just 2 scripts for non-English languages.
            let wallClockTimer: ReturnType<typeof setTimeout> | undefined;
            const wallClockPromise = new Promise<never>((_, reject) => {
              wallClockTimer = setTimeout(() => reject(new Error(`Per-attempt wall-clock timeout exceeded (${WALL_CLOCK_PER_ATTEMPT / 1000}s)`)), WALL_CLOCK_PER_ATTEMPT);
            });
            const aiPromise = generateWithAI(prompt.userPrompt, prompt.systemPrompt, prompt.maxTokens, undefined, undefined, 'ollama', TIMEOUT_PER_CALL, userId);

            let aiResult;
            try {
              aiResult = await Promise.race([aiPromise, wallClockPromise]);
            } finally {
              if (wallClockTimer) clearTimeout(wallClockTimer);
            }
            console.log(`[SalesScript-Streaming] Attempt ${totalAttempts}: AI generation completed in ${Date.now() - attemptStartTime}ms`);
            // Track the AI model/provider used for the response
            lastProvider = aiResult.provider || lastProvider;
            lastAiModel = aiResult.model || lastAiModel;

            const parsed = parseJsonFromAI(aiResult.content);
            if (!parsed) {
              console.warn(`[SalesScript-Streaming] Attempt ${totalAttempts}: AI returned unparseable response`);
              continue;
            }

            // Handle both { scripts: [...] } wrapper and direct object
            const scriptData = Array.isArray(parsed) ? parsed[0] : (parsed.scripts ? parsed.scripts[0] : parsed);

            if (!scriptData || !scriptData.title) {
              console.warn(`[SalesScript-Streaming] Attempt ${totalAttempts}: No title in generated script`);
              continue;
            }

            // Dedup check against batch and DB (language-aware threshold)
            if (isDuplicateOfAny(scriptData, generatedInBatch, undefined, undefined, { language: currentLanguage }) ||
                isDuplicateOfAny(scriptData, existingFieldsForDedup, undefined, undefined, { language: currentLanguage })) {
              console.log(`[SalesScript-Streaming] Attempt ${totalAttempts}: Duplicate detected (threshold: ${Math.round(getSimilarityThreshold(currentLanguage) * 100)}% for ${currentLanguage}), retrying...`);
              continue;
            }

            // Map and save to DB
            const mappedData = computeSalesScriptAutoFillMapping(scriptData);
            if (!mappedData.title) {
              console.warn(`[SalesScript-Streaming] Attempt ${totalAttempts}: Mapping produced no title`);
              continue;
            }

            try {
              const assignedStatus = mappedData.status || 'draft';
              const saved = await SalesScript.create({
                ...mappedData,
                companyId,
                aiGenerated: true,
                aiModel: aiResult.model || lastAiModel,
                provider: aiResult.provider || lastProvider,
                status: assignedStatus,
              });
              savedScriptIds.push(saved._id.toString());
              addJobCompletedScript(job.jobId, saved._id.toString());
              statusBreakdown[assignedStatus] = (statusBreakdown[assignedStatus] || 0) + 1;
            } catch (dbErr: any) {
              console.error(`[SalesScript-Streaming] Failed to save script to DB: ${dbErr.message}`);
            }

            generatedInBatch.push(scriptData);
            existingFieldsForDedup.push({
              openingLine: scriptData.openingLine || '',
              hook: scriptData.hook || '',
              valueProposition: scriptData.valueProposition || '',
            });
            successCount++;

            updateJobProgress(job.jobId, 5 + Math.round((successCount / TARGET_COUNT) * 90), `Generated script ${successCount}/${TARGET_COUNT}`);
          } catch (err: any) {
            const timeElapsed = Date.now() - attemptStartTime;
            const isWallClockTimeout = err.message?.includes('wall-clock timeout');
            console.error(`[SalesScript-Streaming] Attempt ${totalAttempts} failed after ${timeElapsed}ms: ${err.message}`);
            console.error(`[SalesScript-Streaming] Language: ${currentLanguage} | MaxTokens: ${prompt ? prompt.maxTokens : 'unknown'} | CallTimeout: ${TIMEOUT_PER_CALL}ms | WallClock: ${WALL_CLOCK_PER_ATTEMPT}ms`);
            if (!isWallClockTimeout) {
              console.error(`[SalesScript-Streaming] Error stack:`, err.stack);
            }

            // If this is a timeout or critical error, don't retry infinitely
            const isTimeout = err.message?.includes('timeout') || err.message?.includes('abort');
            const isKeyError = err.message?.includes('401') || err.message?.includes('403') || err.message?.includes('authentication');
            const isTruncated = err.message?.includes('truncated') || err.message?.includes('insufficient content');

            if (isWallClockTimeout) {
              console.warn(`[SalesScript-Streaming] Wall-clock timeout on attempt ${totalAttempts} (${(WALL_CLOCK_PER_ATTEMPT / 1000)}s cap). Internal retries consumed too much time; moving to next attempt.`);
            } else if (isTimeout || isKeyError || isTruncated) {
              console.error(`[SalesScript-Streaming] Critical error (${isTimeout ? 'timeout' : isKeyError ? 'auth' : 'truncated'}) on attempt ${totalAttempts}, continuing to next attempt`);
            }

            // Brief pause between attempts so the AI provider isn't hammered
            // with back-to-back requests after a failure (avoids rate-limit cascades).
            await new Promise(resolve => setTimeout(resolve, 1500));
          }
        }

        updateJobProgress(job.jobId, 100, 'Complete');
        completeJob(job.jobId, {
          scriptsGenerated: successCount,
          scriptIds: savedScriptIds,
          statusBreakdown,
          source: 'streaming',
          aiModel: lastAiModel,
          provider: lastProvider,
        }, 'generated');

        console.log(`[SalesScript-Streaming] Job ${job.jobId} completed. ${successCount} unique scripts generated (${totalAttempts} total attempts).`);
      } catch (err: any) {
        console.error(`[SalesScript-Streaming] Job ${job.jobId} failed:`, err.message);
        failJob(job.jobId, err.message || 'AI generation failed');
      }
    });
  }
);

export default router;