/**
 * Speaking Engagement Pipeline
 * Multi-stage pipeline for AI-powered speech generation
 */

import { generateWithAI } from '../../utils/aiProvider';
import { SpeakingEngagementInputs, SpeakingEngagementOutput, buildSpeakingEngagementPrompt, buildSpeakingEngagementRefinementPrompt } from './speakingEngagementPrompts';
import { parseJsonFromAI } from './parseJsonFromAI';

export interface SpeakingEngagementPipelineResult {
  scripts: SpeakingEngagementOutput[];
  pipelineVersion: string;
  provider: string;
  aiModel: string;
  tokensUsed: number;
  inputTokens: number;
  outputTokens: number;
  processingTimeMs: number;
  latencyMs: number;
  overallConfidence: number;
  finishReason: string;
  apiKeyMasked?: string;
}

export class SpeakingEngagementPipeline {
  private inputs: SpeakingEngagementInputs;
  private targetCount: number;
  private progressCallback: (progress: number, step: string) => void;
  private startTime: number;

  // Token/provider tracking accumulated across all generateWithAI calls.
  private totalTokens = 0;
  private totalInputTokens = 0;
  private totalOutputTokens = 0;
  private lastProvider = 'unknown';
  private lastAiModel = 'unknown';
  private lastFinishReason: string | null = null;
  private lastApiKeyMasked: string | null = null;

  constructor(inputs: SpeakingEngagementInputs, progressCallback?: (progress: number, step: string) => void) {
    this.inputs = inputs;
    this.targetCount = inputs.targetCount || 1;
    this.progressCallback = progressCallback || (() => {});
    this.startTime = Date.now();
  }

  /** Accumulate token usage + provider/model metadata from an AI response. */
  private accumulate(response: any): void {
    if (!response) return;
    if (response.provider) this.lastProvider = response.provider;
    if (response.model) this.lastAiModel = response.model;
    this.totalTokens += response.tokenUsage?.totalTokens ?? 0;
    this.totalInputTokens += response.tokenUsage?.inputTokens ?? 0;
    this.totalOutputTokens += response.tokenUsage?.outputTokens ?? 0;
    if (response.finishReason) this.lastFinishReason = response.finishReason;
    if (response.keyUsed) this.lastApiKeyMasked = response.keyUsed;
  }

  async run(): Promise<SpeakingEngagementPipelineResult> {
    const pipelineStart = Date.now();
    const allScripts: SpeakingEngagementOutput[] = [];

    // Stage 1: Generate speech content
    this.progressCallback(10, 'Generating speech content...');
    const generationResult = await this.generateSpeechContent();
    if (generationResult) {
      allScripts.push(...(Array.isArray(generationResult) ? generationResult : [generationResult]));
    }

    // Stage 2: Refine with brand guardrails if harmony context is available.
    // Each script is refined independently, so run the per-script AI calls
    // concurrently instead of serially — N scripts previously meant N sequential
    // AI calls, each with a 15-minute floor timeout, which pushed total wall-clock
    // past the frontend's 20-minute poll cap and surfaced as a "session timeout".
    // Failures remain non-fatal per script (fall back to the unrefined script).
    if (this.inputs.harmonyText && allScripts.length > 0) {
      this.progressCallback(50, 'Refining with brand guidelines...');
      const refinedScripts = await Promise.all(
        allScripts.map(async (script, i) => {
          try {
            const refined = await this.refineWithBrand(script);
            return refined || script;
          } catch (err: any) {
            console.warn(`[SpeakingEngagement-Pipeline] Brand refinement failed for script ${i + 1}: ${err.message}`);
            return script;
          }
        })
      );
      for (let i = 0; i < refinedScripts.length; i++) allScripts[i] = refinedScripts[i];
    }

    // Stage 3: Generate additional structured outputs if missing — also
    // independent per script, so run concurrently. Preserves the original
    // "only enrich when speakerNotes/confidenceTips are missing" guard.
    this.progressCallback(75, 'Generating speaker notes and stage instructions...');
    const enrichedScripts = await Promise.all(
      allScripts.map(async (script, i) => {
        if (script.speakerNotes && script.confidenceTips) return script;
        try {
          const enriched = await this.enrichWithStructure(script);
          return enriched || script;
        } catch (err: any) {
          console.warn(`[SpeakingEngagement-Pipeline] Structure enrichment failed for script ${i + 1}: ${err.message}`);
          return script;
        }
      })
    );
    for (let i = 0; i < enrichedScripts.length; i++) allScripts[i] = enrichedScripts[i];

    this.progressCallback(95, 'Finalising...');
    const pipelineEnd = Date.now();

    return {
      scripts: allScripts,
      pipelineVersion: '1.0.0',
      provider: this.lastProvider,
      aiModel: this.lastAiModel,
      tokensUsed: this.totalTokens,
      inputTokens: this.totalInputTokens,
      outputTokens: this.totalOutputTokens,
      processingTimeMs: pipelineEnd - pipelineStart,
      latencyMs: pipelineEnd - this.startTime,
      overallConfidence: allScripts.length > 0 ? 0.85 : 0,
      finishReason: this.lastFinishReason || 'stop',
      apiKeyMasked: this.lastApiKeyMasked || undefined,
    };
  }

  private async generateSpeechContent(): Promise<SpeakingEngagementOutput[] | null> {
    const { systemPrompt, userPrompt, maxTokens } = buildSpeakingEngagementPrompt(this.inputs);

    try {
      const response = await generateWithAI(userPrompt, systemPrompt, maxTokens, 0.85, 'json');
      this.accumulate(response);

      if (!response || !response.content) {
        console.warn('[SpeakingEngagement-Pipeline] No response from AI for content generation');
        return null;
      }

      const parsed = parseJsonFromAI(response.content);
      if (!parsed) {
        // Log a snippet of the raw response so we can diagnose JSON parse failures
        const snippet = response.content.substring(0, 500);
        console.warn('[SpeakingEngagement-Pipeline] Failed to parse AI response as JSON. Raw response snippet:', snippet);
        console.warn('[SpeakingEngagement-Pipeline] Full response length:', response.content.length, 'chars');
        return null;
      }

      // Normalise to array
      const scripts = Array.isArray(parsed) ? parsed : [parsed];

      // Ensure required fields
      // CRITICAL: Always use this.inputs.language, NOT script.language.
      // The AI model often returns 'hi' or 'en' regardless of user selection.
      // The user's explicit language choice must take absolute priority.
      return scripts.map((script: any) => ({
        name: script.name || `${this.getSpeechTypeLabel()} — ${this.inputs.speakerName || 'Speech'}`,
        speechType: script.speechType || this.inputs.speechType,
        audienceType: script.audienceType || this.inputs.audienceType || 'General audience',
        language: this.inputs.language, // ALWAYS use user's selection — never trust AI output for language
        duration: script.duration || this.inputs.duration,
        speakerType: script.speakerType || this.inputs.speakerType,
        tone: script.tone || this.inputs.tone,
        content: script.content || '',
        structure: script.structure || undefined,
        speakerNotes: script.speakerNotes || undefined,
        stageInstructions: script.stageInstructions || undefined,
        confidenceTips: script.confidenceTips || undefined,
        specialOutputs: script.specialOutputs || undefined,
        status: script.status || 'draft',
      }));
    } catch (err: any) {
      console.error(`[SpeakingEngagement-Pipeline] Content generation failed: ${err.message}`);
      return null;
    }
  }

  private async refineWithBrand(script: SpeakingEngagementOutput): Promise<SpeakingEngagementOutput | null> {
    const { systemPrompt, userPrompt, maxTokens } = buildSpeakingEngagementRefinementPrompt(
      JSON.stringify(script),
      'improve',
      this.inputs
    );

    try {
      const response = await generateWithAI(userPrompt, systemPrompt, maxTokens, 0.85, 'json');
      this.accumulate(response);

      if (!response || !response.content) return null;

      const parsed = parseJsonFromAI(response.content);
      if (!parsed) return null;

      return {
        ...script,
        ...parsed,
        // Preserve metadata
        name: parsed.name || script.name,
        speechType: parsed.speechType || script.speechType,
      };
    } catch (err: any) {
      console.warn(`[SpeakingEngagement-Pipeline] Brand refinement error: ${err.message}`);
      return null;
    }
  }

  private async enrichWithStructure(script: SpeakingEngagementOutput): Promise<SpeakingEngagementOutput | null> {
    const languageMap: Record<string, string> = {
      'en': 'English', 'hi': 'Hindi', 'mr': 'Marathi', 'es': 'Spanish', 'fr': 'French',
      'de': 'German', 'pt': 'Portuguese', 'it': 'Italian', 'nl': 'Dutch', 'ru': 'Russian',
      'ja': 'Japanese', 'ko': 'Korean', 'zh': 'Chinese (Mandarin)', 'ar': 'Arabic', 'tr': 'Turkish',
      'pl': 'Polish', 'sv': 'Swedish', 'no': 'Norwegian', 'da': 'Danish', 'fi': 'Finnish',
      'cs': 'Czech', 'el': 'Greek', 'he': 'Hebrew', 'th': 'Thai', 'vi': 'Vietnamese',
      'id': 'Indonesian', 'ms': 'Malay', 'fil': 'Filipino', 'bn': 'Bengali', 'ur': 'Urdu',
      'ta': 'Tamil', 'te': 'Telugu', 'kn': 'Kannada', 'ml': 'Malayalam', 'pa': 'Punjabi',
      'gu': 'Gujarati', 'sw': 'Swahili', 'am': 'Amharic',
    };
    const languageLabel = languageMap[script.language] || script.language || 'English';
    const languageInstruction = languageLabel !== 'English'
      ? `\n\nCRITICAL OVERRIDE: You MUST write ALL speaker notes, stage instructions, and confidence tips ENTIRELY in ${languageLabel}. Do NOT output in English unless English is explicitly requested. Every single word must be in ${languageLabel}. Only JSON field names may be in English.`
      : '';

    const systemPrompt = `You are a public speaking coach. Given a speech, generate detailed speaker notes, stage instructions, and confidence tips. Write in ${languageLabel}.${languageInstruction}

OUTPUT FORMAT — CRITICAL: You MUST respond with ONLY a valid JSON object. Do NOT include any thinking, reasoning, chain-of-thought, analysis, or explanatory text before or after the JSON. Output the JSON object directly starting with { and ending with }. No markdown code fences. No preamble. No commentary.

Respond with ONLY valid JSON matching this structure:
{
  "speakerNotes": {
    "deliveryGuidance": ["3-5 specific delivery tips"],
    "emphasisPoints": ["3-5 phrases to emphasise"],
    "pausePoints": ["3-5 pause moments"],
    "audienceEngagement": ["2-3 engagement techniques"]
  },
  "stageInstructions": {
    "pausePoints": [{"timestamp": "after opening", "instruction": "pause for effect"}],
    "emphasisPoints": [{"phrase": "key phrase", "intensity": "moderate"}],
    "audienceEngagementMoments": [{"moment": "during story", "technique": "ask a question"}]
  },
  "confidenceTips": {
    "deliverySuggestions": ["3-5 practical delivery tips"],
    "bodyLanguageTips": ["3-5 body language tips"],
    "vocalVariationTips": ["3-5 vocal variety tips"]
  }
}`;

    const userPrompt = `Speech content:\n${script.content}\n\nSpeech type: ${script.speechType}\nDuration: ${script.duration}\nTone: ${script.tone}\nLanguage: ${languageLabel}\n\nGenerate detailed speaker notes, stage instructions, and confidence tips for this speech. Respond with ONLY valid JSON.`;

    try {
      const response = await generateWithAI(userPrompt, systemPrompt, 16000, 0.85, 'json');
      this.accumulate(response);

      if (!response || !response.content) return null;

      const parsed = parseJsonFromAI(response.content);
      if (!parsed) return null;

      return {
        ...script,
        speakerNotes: parsed.speakerNotes || script.speakerNotes,
        stageInstructions: parsed.stageInstructions || script.stageInstructions,
        confidenceTips: parsed.confidenceTips || script.confidenceTips,
      };
    } catch (err: any) {
      console.warn(`[SpeakingEngagement-Pipeline] Structure enrichment error: ${err.message}`);
      return null;
    }
  }

  private getSpeechTypeLabel(): string {
    const labels: Record<string, string> = {
      'tedx-speech': 'TEDx Speech',
      'josh-talks-speech': 'Josh Talks Speech',
      'one-line-speech': 'One-Line Speech',
      'storytelling-speech': 'Storytelling Speech',
      'elevator-pitch': 'Elevator Pitch',
      'keynote-speech': 'Keynote Speech',
      'event-speech': 'Event Speech',
      'motivational-speech': 'Motivational Speech',
      'company-introduction': 'Company Introduction',
      'founder-introduction': 'Founder Introduction',
      'employee-introduction': 'Employee Introduction',
      'product-launch-speech': 'Product Launch Speech',
      'award-acceptance': 'Award Acceptance Speech',
      'investor-pitch-speech': 'Investor Pitch Speech',
      'networking-introduction': 'Networking Introduction',
      'custom-speech': 'Custom Speech',
    };
    return labels[this.inputs.speechType] || 'Speech';
  }
}