/**
 * Audio Content AI Pipeline
 *
 * 3-step generation workflow:
 * Step 1 (AudioContentPipeline): Generate song content — concept, lyrics, chorus, verses, bridge, genre, mood, tags
 * Step 2 (AudioContentPromptPipeline): Generate optimized Suno prompt from edited lyrics + context
 * Step 3: Suno audio generation (handled by route + sunoService, not this pipeline)
 *
 * Enhancement: Retry low-confidence fields in each pipeline
 */

import { generateWithAI } from '../../utils/aiProvider';
import { parseJsonFromAI } from './parseJsonFromAI';
import {
  buildAudioContentLyricsPrompt,
  buildAudioContentPromptGenerationPrompt,
  buildAudioContentEnhancementPrompt,
  AudioContentPipelineInputs,
  AudioContentPromptInputs,
} from './audioContentPrompts';

// ============================================
// TYPES
// ============================================

export interface StageResult {
  stage: string;
  success: boolean;
  rawOutput: string;
  parsedOutput: Record<string, any> | null;
  provider: string;
  model: string;
  inputTokens: number;
  outputTokens: number;
  latencyMs: number;
  confidence: number;
  finishReason: string | null;
  apiKeyMasked: string | null;
  error?: string;
}

export interface AudioContentPipelineResult {
  songTitle: string;
  songConcept: string;
  lyrics: string;
  chorus: string;
  verses: string[];
  bridge: string;
  genreSuggestions: string[];
  mood: string;
  musicTags: string[];
  pipelineVersion: string;
  provider: string;
  aiModel: string;
  tokensUsed: number;
  inputTokens: number;
  outputTokens: number;
  processingTimeMs: number;
  latencyMs: number;
  overallConfidence: number;
  finishReason: string | null;
  apiKeyMasked: string | null;
  stageResults: StageResult[];
  errors: string[];
}

export interface AudioContentPromptPipelineResult {
  optimizedSunoPrompt: string;
  suggestedTags: string[];
  confidence: number;
  pipelineVersion: string;
  provider: string;
  aiModel: string;
  tokensUsed: number;
  inputTokens: number;
  outputTokens: number;
  processingTimeMs: number;
  latencyMs: number;
  overallConfidence: number;
  finishReason: string | null;
  apiKeyMasked: string | null;
  stageResults: StageResult[];
  errors: string[];
}

// ============================================
// CONSTANTS
// ============================================

const PIPELINE_VERSION = '2.0';
const LOW_CONFIDENCE_THRESHOLD = 0.6;

// ============================================
// STEP 1: LYRICS/CONTENT PIPELINE
// ============================================

export class AudioContentPipeline {
  private inputs: AudioContentPipelineInputs;
  private companyId: string;
  private moduleId: string;
  private preferredProvider: string;
  private preferredModel: string;
  private providerLock: boolean;
  private accumulated: Record<string, any> = {};
  private stageResults: StageResult[] = [];
  private errors: string[] = [];
  public onProgress?: (progress: number, step: string) => void;

  constructor(
    inputs: AudioContentPipelineInputs,
    companyId: string,
    moduleId: string = 'audio-content',
    preferredProvider: string = 'auto',
    preferredModel: string = '',
    providerLock: boolean = false
  ) {
    this.inputs = inputs;
    this.companyId = companyId;
    this.moduleId = moduleId;
    this.preferredProvider = preferredProvider;
    this.preferredModel = preferredModel;
    this.providerLock = providerLock;
  }

  async run(): Promise<AudioContentPipelineResult> {
    const startTime = Date.now();

    // Stage 1: Generate song content (lyrics, concept, chorus, verses, bridge, genre, mood, tags)
    this.onProgress?.(5, 'Analyzing song requirements...');
    await this.executeStageWithRetry(
      buildAudioContentLyricsPrompt(this.inputs),
      'audio-content-lyrics',
      0
    );
    this.onProgress?.(70, 'Content generated');

    // Enhancement if needed
    const lowFields = this.getLowConfidenceFields('audio-content-lyrics');
    if (lowFields.length > 0) {
      this.onProgress?.(80, 'Enhancing low-confidence fields...');
      await this.enhanceStage('audio-content-lyrics', lowFields);
    }

    this.onProgress?.(95, 'Finalizing audio content');

    const totalTime = Date.now() - startTime;
    const totalInputTokens = this.stageResults.reduce((sum, s) => sum + s.inputTokens, 0);
    const totalOutputTokens = this.stageResults.reduce((sum, s) => sum + s.outputTokens, 0);
    const avgConfidence =
      this.stageResults.length > 0
        ? this.stageResults.reduce((sum, s) => sum + s.confidence, 0) / this.stageResults.length
        : 0;
    const lastStage = this.stageResults[this.stageResults.length - 1];

    return {
      songTitle: this.accumulated.songTitle || this.inputs.songTitle,
      songConcept: this.accumulated.songConcept || '',
      lyrics: this.accumulated.lyrics || '',
      chorus: this.accumulated.chorus || '',
      verses: this.accumulated.verses || [],
      bridge: this.accumulated.bridge || '',
      genreSuggestions: this.accumulated.genreSuggestions || [],
      mood: this.accumulated.mood || this.inputs.mood || '',
      musicTags: this.accumulated.musicTags || [],
      pipelineVersion: PIPELINE_VERSION,
      provider: lastStage?.provider || 'unknown',
      aiModel: lastStage?.model || 'unknown',
      tokensUsed: totalInputTokens + totalOutputTokens,
      inputTokens: totalInputTokens,
      outputTokens: totalOutputTokens,
      processingTimeMs: totalTime,
      latencyMs: lastStage?.latencyMs || 0,
      overallConfidence: avgConfidence,
      finishReason: lastStage?.finishReason || null,
      apiKeyMasked: lastStage?.apiKeyMasked || null,
      stageResults: this.stageResults,
      errors: this.errors,
    };
  }

  // ============================================
  // STAGE EXECUTION
  // ============================================

  private async executeStage(
    promptConfig: { systemPrompt: string; userPrompt: string; maxTokens: number },
    stageName: string,
    stageIndex: number
  ): Promise<StageResult> {
    const stageStart = Date.now();

    try {
      const result = await generateWithAI(
        promptConfig.userPrompt,
        promptConfig.systemPrompt,
        promptConfig.maxTokens,
        undefined, // temperature
        'json',    // format
        this.preferredProvider as any,
        undefined, // timeoutOverride
        undefined, // userId
        this.companyId,
        this.providerLock
      );

      const latency = Date.now() - stageStart;
      const parsed = parseJsonFromAI(result.content);

      if (parsed) {
        Object.assign(this.accumulated, parsed);
      }

      const stageResult: StageResult = {
        stage: stageName,
        success: true,
        rawOutput: result.content,
        parsedOutput: parsed,
        provider: result.provider || 'unknown',
        model: result.model || 'unknown',
        inputTokens: result.tokenUsage?.inputTokens ?? 0,
        outputTokens: result.tokenUsage?.outputTokens ?? 0,
        latencyMs: latency,
        confidence: parsed ? 0.85 : 0.3,
        finishReason: result.finishReason || null,
        apiKeyMasked: result.keyUsed || null,
      };

      this.stageResults.push(stageResult);
      return stageResult;
    } catch (error: any) {
      const stageResult: StageResult = {
        stage: stageName,
        success: false,
        rawOutput: '',
        parsedOutput: null,
        provider: 'unknown',
        model: 'unknown',
        inputTokens: 0,
        outputTokens: 0,
        latencyMs: Date.now() - stageStart,
        confidence: 0,
        finishReason: null,
        apiKeyMasked: null,
        error: error.message,
      };

      this.errors.push(`Stage "${stageName}" failed: ${error.message}`);
      this.stageResults.push(stageResult);
      return stageResult;
    }
  }

  private async executeStageWithRetry(
    promptConfig: { systemPrompt: string; userPrompt: string; maxTokens: number },
    stageName: string,
    stageIndex: number,
    maxRetries: number = 1
  ): Promise<StageResult> {
    let result = await this.executeStage(promptConfig, stageName, stageIndex);

    if (!result.success && maxRetries > 0) {
      this.onProgress?.(30, `Retrying ${stageName}...`);
      result = await this.executeStage(promptConfig, stageName, stageIndex);
    }

    return result;
  }

  // ============================================
  // ENHANCEMENT
  // ============================================

  private getLowConfidenceFields(stageName: string): string[] {
    const stage = this.stageResults.find(s => s.stage === stageName);
    if (!stage || !stage.parsedOutput) return [];

    const lowFields: string[] = [];
    for (const [key, value] of Object.entries(stage.parsedOutput)) {
      if (!value || (typeof value === 'string' && value.trim().length < 10)) {
        lowFields.push(key);
      }
    }

    return lowFields;
  }

  private async enhanceStage(
    stageName: string,
    lowConfidenceFields: string[]
  ): Promise<void> {
    const enhancementPrompt = buildAudioContentEnhancementPrompt(
      this.accumulated,
      lowConfidenceFields
    );

    const result = await this.executeStage(enhancementPrompt, `${stageName}-enhancement`, 0);

    if (result.parsedOutput) {
      for (const field of lowConfidenceFields) {
        if (result.parsedOutput[field] !== undefined) {
          this.accumulated[field] = result.parsedOutput[field];
        }
      }
    }
  }
}

// ============================================
// STEP 2: SUNO PROMPT GENERATION PIPELINE
// ============================================

export class AudioContentPromptPipeline {
  private inputs: AudioContentPromptInputs;
  private companyId: string;
  private moduleId: string;
  private preferredProvider: string;
  private preferredModel: string;
  private providerLock: boolean;
  private accumulated: Record<string, any> = {};
  private stageResults: StageResult[] = [];
  private errors: string[] = [];
  public onProgress?: (progress: number, step: string) => void;

  constructor(
    inputs: AudioContentPromptInputs,
    companyId: string,
    moduleId: string = 'audio-content',
    preferredProvider: string = 'auto',
    preferredModel: string = '',
    providerLock: boolean = false
  ) {
    this.inputs = inputs;
    this.companyId = companyId;
    this.moduleId = moduleId;
    this.preferredProvider = preferredProvider;
    this.preferredModel = preferredModel;
    this.providerLock = providerLock;
  }

  async run(): Promise<AudioContentPromptPipelineResult> {
    const startTime = Date.now();

    // Single stage: Generate optimized Suno prompt
    this.onProgress?.(10, 'Generating optimized Suno prompt...');
    await this.executeStageWithRetry(
      buildAudioContentPromptGenerationPrompt(this.inputs),
      'audio-content-prompt',
      0
    );
    this.onProgress?.(70, 'Suno prompt generated');

    // Enhancement if needed
    const lowFields = this.getLowConfidenceFields('audio-content-prompt');
    if (lowFields.length > 0) {
      this.onProgress?.(80, 'Enhancing low-confidence fields...');
      await this.enhanceStage('audio-content-prompt', lowFields);
    }

    this.onProgress?.(95, 'Finalizing prompt');

    const totalTime = Date.now() - startTime;
    const totalInputTokens = this.stageResults.reduce((sum, s) => sum + s.inputTokens, 0);
    const totalOutputTokens = this.stageResults.reduce((sum, s) => sum + s.outputTokens, 0);
    const avgConfidence =
      this.stageResults.length > 0
        ? this.stageResults.reduce((sum, s) => sum + s.confidence, 0) / this.stageResults.length
        : 0;
    const lastStage = this.stageResults[this.stageResults.length - 1];

    return {
      optimizedSunoPrompt: this.accumulated.optimizedSunoPrompt || '',
      suggestedTags: this.accumulated.suggestedTags || [],
      confidence: this.accumulated.confidence || avgConfidence,
      pipelineVersion: PIPELINE_VERSION,
      provider: lastStage?.provider || 'unknown',
      aiModel: lastStage?.model || 'unknown',
      tokensUsed: totalInputTokens + totalOutputTokens,
      inputTokens: totalInputTokens,
      outputTokens: totalOutputTokens,
      processingTimeMs: totalTime,
      latencyMs: lastStage?.latencyMs || 0,
      overallConfidence: avgConfidence,
      finishReason: lastStage?.finishReason || null,
      apiKeyMasked: lastStage?.apiKeyMasked || null,
      stageResults: this.stageResults,
      errors: this.errors,
    };
  }

  // ============================================
  // STAGE EXECUTION (same pattern as AudioContentPipeline)
  // ============================================

  private async executeStage(
    promptConfig: { systemPrompt: string; userPrompt: string; maxTokens: number },
    stageName: string,
    stageIndex: number
  ): Promise<StageResult> {
    const stageStart = Date.now();

    try {
      const result = await generateWithAI(
        promptConfig.userPrompt,
        promptConfig.systemPrompt,
        promptConfig.maxTokens,
        undefined,
        'json',
        this.preferredProvider as any,
        undefined,
        undefined,
        this.companyId,
        this.providerLock
      );

      const latency = Date.now() - stageStart;
      const parsed = parseJsonFromAI(result.content);

      if (parsed) {
        Object.assign(this.accumulated, parsed);
      }

      const stageResult: StageResult = {
        stage: stageName,
        success: true,
        rawOutput: result.content,
        parsedOutput: parsed,
        provider: result.provider || 'unknown',
        model: result.model || 'unknown',
        inputTokens: result.tokenUsage?.inputTokens ?? 0,
        outputTokens: result.tokenUsage?.outputTokens ?? 0,
        latencyMs: latency,
        confidence: parsed ? 0.85 : 0.3,
        finishReason: result.finishReason || null,
        apiKeyMasked: result.keyUsed || null,
      };

      this.stageResults.push(stageResult);
      return stageResult;
    } catch (error: any) {
      const stageResult: StageResult = {
        stage: stageName,
        success: false,
        rawOutput: '',
        parsedOutput: null,
        provider: 'unknown',
        model: 'unknown',
        inputTokens: 0,
        outputTokens: 0,
        latencyMs: Date.now() - stageStart,
        confidence: 0,
        finishReason: null,
        apiKeyMasked: null,
        error: error.message,
      };

      this.errors.push(`Stage "${stageName}" failed: ${error.message}`);
      this.stageResults.push(stageResult);
      return stageResult;
    }
  }

  private async executeStageWithRetry(
    promptConfig: { systemPrompt: string; userPrompt: string; maxTokens: number },
    stageName: string,
    stageIndex: number,
    maxRetries: number = 1
  ): Promise<StageResult> {
    let result = await this.executeStage(promptConfig, stageName, stageIndex);

    if (!result.success && maxRetries > 0) {
      this.onProgress?.(30, `Retrying ${stageName}...`);
      result = await this.executeStage(promptConfig, stageName, stageIndex);
    }

    return result;
  }

  private getLowConfidenceFields(stageName: string): string[] {
    const stage = this.stageResults.find(s => s.stage === stageName);
    if (!stage || !stage.parsedOutput) return [];

    const lowFields: string[] = [];
    for (const [key, value] of Object.entries(stage.parsedOutput)) {
      if (!value || (typeof value === 'string' && value.trim().length < 10)) {
        lowFields.push(key);
      }
    }

    return lowFields;
  }

  private async enhanceStage(
    stageName: string,
    lowConfidenceFields: string[]
  ): Promise<void> {
    const enhancementPrompt = buildAudioContentEnhancementPrompt(
      this.accumulated,
      lowConfidenceFields
    );

    const result = await this.executeStage(enhancementPrompt, `${stageName}-enhancement`, 0);

    if (result.parsedOutput) {
      for (const field of lowConfidenceFields) {
        if (result.parsedOutput[field] !== undefined) {
          this.accumulated[field] = result.parsedOutput[field];
        }
      }
    }
  }
}