/**
 * Case Study AI Pipeline
 *
 * Orchestrates a 3-stage AI analysis pipeline for case study generation.
 * Generates multiple (default 4) diverse case studies in one pass.
 * Uses company context, ICP data, and brand strategy as seed input.
 */

import { generateWithAI } from '../../utils/aiProvider';
import { parseJsonFromAI } from './parseJsonFromAI';
import {
  buildCaseStudyFoundationPrompt,
  buildCaseStudyContentPrompt,
  buildCaseStudyKpisSeoPrompt,
  buildCaseStudyEnhancementPrompt,
  CaseStudyPipelineInputs,
  PromptResult,
} from './caseStudyPrompts';

// ============================================
// TYPES
// ============================================

export interface CaseStudyPipelineResult {
  caseStudies: Record<string, any>[];
  pipelineVersion: string;
  provider: string;
  aiModel: string;
  tokensUsed: number;
  inputTokens: number;
  outputTokens: number;
  processingTimeMs: number;
  latencyMs: number;
  overallConfidence: number;
  fieldConfidences: Record<string, number>;
  finishReason: string | null;
  apiKeyMasked: string | null;
  stageResults: StageResult[];
  errors: string[];
}

export interface StageResult {
  stage: string;
  success: boolean;
  provider?: string;
  aiModel?: string;
  tokensUsed?: number;
  error?: string;
  duration: number;
  enhanced?: boolean;
}

const PIPELINE_VERSION = '2.0';
const CONFIDENCE_THRESHOLD = 60;

// ============================================
// JSON PARSING UTILITY
// ============================================

function extractFieldsFromRawContent(content: string): Record<string, any> {
  const result: Record<string, any> = {};
  if (!content || typeof content !== 'string') return result;

  const stringPairRegex = /"(\w+)":\s*"((?:[^"\\]|\\.)*)"/g;
  let match;
  while ((match = stringPairRegex.exec(content)) !== null) {
    const [, key, value] = match;
    result[key] = value.replace(/\\"/g, '"').replace(/\\n/g, '\n');
  }

  const arrayPairRegex = /"(\w+)":\s*\[((?:\s*"(?:[^"\\]|\\.)*"\s*,?\s*)+)\]/g;
  while ((match = arrayPairRegex.exec(content)) !== null) {
    const [, key, arrayContent] = match;
    const values = [...arrayContent.matchAll(/"((?:[^"\\]|\\.)*)"/g)].map(m => m[1]);
    if (values.length > 0) {
      result[key] = values;
    }
  }

  const nestedObjRegex = /"(\w+)":\s*(\{[^}]*\})/g;
  while ((match = nestedObjRegex.exec(content)) !== null) {
    const [, key, objStr] = match;
    try {
      result[key] = JSON.parse(objStr);
    } catch {
      // Skip unparseable nested objects
    }
  }

  return result;
}

/**
 * Extract case study array from parsed response.
 * Handles both array responses and { caseStudies: [...] } wrapper format.
 */
function extractCaseStudyArray(parsed: Record<string, any>): Record<string, any>[] {
  if (Array.isArray(parsed)) {
    return parsed;
  }
  if (parsed.caseStudies && Array.isArray(parsed.caseStudies)) {
    return parsed.caseStudies;
  }
  // Single case study format (fallback) — wrap in array
  return [parsed];
}

// ============================================
// PIPELINE CLASS
// ============================================

export class CaseStudyPipeline {
  private inputs: CaseStudyPipelineInputs;
  private caseStudyAccumulated: Record<string, any>[];
  private stageResults: StageResult[];
  private errors: string[];
  private totalTokens: number;
  private totalInputTokens: number;
  private totalOutputTokens: number;
  private totalLatencyMs: number;
  private lastFinishReason: string | null;
  private lastApiKeyMasked: string | null;
  private startTime: number;
  private lastProvider: string;
  private lastAiModel: string;
  private targetCount: number;
  private onProgress?: (progress: number, step: string) => void;

  constructor(inputs: CaseStudyPipelineInputs, onProgress?: (progress: number, step: string) => void) {
    this.inputs = inputs;
    this.targetCount = inputs.targetCount || 1;
    this.caseStudyAccumulated = Array.from({ length: this.targetCount }, () => ({}));
    this.stageResults = [];
    this.errors = [];
    this.totalTokens = 0;
    this.totalInputTokens = 0;
    this.totalOutputTokens = 0;
    this.totalLatencyMs = 0;
    this.lastFinishReason = null;
    this.lastApiKeyMasked = null;
    this.startTime = Date.now();
    this.lastProvider = 'unknown';
    this.lastAiModel = 'unknown';
    this.onProgress = onProgress;
  }

  async run(): Promise<CaseStudyPipelineResult> {
    // Stage 1: Generate foundations for all case studies at once
    this.onProgress?.(5, 'Building case study foundations...');
    await this.runStageWithRetry('case-study-foundation', () =>
      this.executeFoundationStage()
    );
    this.onProgress?.(35, 'Foundations defined');

    // Stage 2: Generate content for all case studies at once
    this.onProgress?.(40, 'Generating case study content...');
    await this.runStageWithRetry('case-study-content', () =>
      this.executeContentStage()
    );
    this.onProgress?.(65, 'Content generated');

    // Stage 3: Generate KPIs/SEO for all case studies at once
    this.onProgress?.(70, 'Optimizing KPIs & SEO...');
    await this.runStageWithRetry('case-study-kpis-seo', () =>
      this.executeKpisSeoStage()
    );
    this.onProgress?.(95, 'Finalizing case studies');

    const overallConfidence = this.computeOverallConfidence();
    const fieldConfidences = this.extractFieldConfidences();

    return {
      caseStudies: this.caseStudyAccumulated,
      pipelineVersion: PIPELINE_VERSION,
      provider: this.lastProvider,
      aiModel: this.lastAiModel,
      tokensUsed: this.totalTokens,
      inputTokens: this.totalInputTokens,
      outputTokens: this.totalOutputTokens,
      processingTimeMs: Date.now() - this.startTime,
      latencyMs: this.totalLatencyMs,
      overallConfidence,
      fieldConfidences,
      finishReason: this.lastFinishReason,
      apiKeyMasked: this.lastApiKeyMasked,
      stageResults: this.stageResults,
      errors: this.errors,
    };
  }

  /**
   * AI execution details known *so far*. Available as soon as the first stage
   * returns, so the AI Processing screen can show the real provider/model and
   * running token counts while the job is still in flight instead of a
   * placeholder.
   */
  getLiveMetadata() {
    return {
      provider: this.lastProvider !== 'unknown' ? this.lastProvider : undefined,
      model: this.lastAiModel !== 'unknown' ? this.lastAiModel : undefined,
      inputTokens: this.totalInputTokens || null,
      outputTokens: this.totalOutputTokens || null,
      totalTokens: this.totalTokens || null,
      latencyMs: this.totalLatencyMs || null,
      durationMs: Date.now() - this.startTime,
      finishReason: this.lastFinishReason,
      apiKeyMasked: this.lastApiKeyMasked,
    };
  }

  // ============================================
  // STAGE EXECUTION
  // ============================================

  private async executeFoundationStage(): Promise<void> {
    console.log(`[CaseStudy-Pipeline] Executing foundation stage for ${this.targetCount} case studies`);
    const promptConfig = buildCaseStudyFoundationPrompt(this.inputs, this.targetCount);
    const result = await generateWithAI(
      promptConfig.userPrompt,
      promptConfig.systemPrompt,
      promptConfig.maxTokens
    );

    this.lastProvider = result.provider;
    this.lastAiModel = result.model;
    this.totalTokens += result.tokenUsage?.totalTokens ?? 0;
    this.totalInputTokens += result.tokenUsage?.inputTokens ?? 0;
    this.totalOutputTokens += result.tokenUsage?.outputTokens ?? 0;
    if (result.latencyMs) this.totalLatencyMs += result.latencyMs;
    if (result.finishReason) this.lastFinishReason = result.finishReason;
    if (result.keyUsed) this.lastApiKeyMasked = result.keyUsed;

    console.log(`[CaseStudy-Pipeline] Foundation stage response received. Provider: ${result.provider}, Model: ${result.model}, Content length: ${result.content?.length || 0}`);

    const parsed = parseJsonFromAI(result.content);
    if (!parsed) {
      const extracted = extractFieldsFromRawContent(result.content);
      if (Object.keys(extracted).length > 0) {
        console.warn('[CaseStudy-Pipeline] Foundation stage: JSON parsing failed, extracted fields as single case study');
        this.mergeSingleIntoFirst(extracted);
        return;
      }
      throw new Error('AI response could not be parsed as JSON for foundation stage');
    }

    const caseStudies = extractCaseStudyArray(parsed);
    console.log(`[CaseStudy-Pipeline] Foundation stage: parsed ${caseStudies.length} case studies`);

    // Merge each case study's foundation into accumulated array
    for (let i = 0; i < caseStudies.length && i < this.targetCount; i++) {
      this.mergeIntoCaseStudy(i, caseStudies[i]);
    }

    // If we got fewer than targetCount, duplicate or generate placeholders
    while (this.caseStudyAccumulated.filter(cs => Object.keys(cs).length > 0).length < this.targetCount && caseStudies.length > 0) {
      const existingCount = this.caseStudyAccumulated.filter(cs => Object.keys(cs).length > 0).length;
      if (existingCount >= this.targetCount) break;
      // Duplicate the last case study with a variant suffix
      const lastCs = caseStudies[caseStudies.length - 1];
      const variant = { ...lastCs, title: `${lastCs.title} (Variant ${existingCount + 1})` };
      this.mergeIntoCaseStudy(existingCount, variant);
    }
  }

  private async executeContentStage(): Promise<void> {
    console.log(`[CaseStudy-Pipeline] Executing content stage for ${this.targetCount} case studies`);
    const foundations = this.caseStudyAccumulated.map(cs => ({ ...cs }));
    const promptConfig = buildCaseStudyContentPrompt(this.inputs, foundations);
    const result = await generateWithAI(
      promptConfig.userPrompt,
      promptConfig.systemPrompt,
      promptConfig.maxTokens
    );

    this.lastProvider = result.provider;
    this.lastAiModel = result.model;
    this.totalTokens += result.tokenUsage?.totalTokens ?? 0;
    this.totalInputTokens += result.tokenUsage?.inputTokens ?? 0;
    this.totalOutputTokens += result.tokenUsage?.outputTokens ?? 0;
    if (result.latencyMs) this.totalLatencyMs += result.latencyMs;
    if (result.finishReason) this.lastFinishReason = result.finishReason;
    if (result.keyUsed) this.lastApiKeyMasked = result.keyUsed;

    console.log(`[CaseStudy-Pipeline] Content stage response received. Provider: ${result.provider}, Model: ${result.model}, Content length: ${result.content?.length || 0}`);

    const parsed = parseJsonFromAI(result.content);
    if (!parsed) {
      const extracted = extractFieldsFromRawContent(result.content);
      if (Object.keys(extracted).length > 0) {
        console.warn('[CaseStudy-Pipeline] Content stage: JSON parsing failed, extracted fields as single case study');
        this.mergeSingleIntoFirst(extracted);
        return;
      }
      throw new Error('AI response could not be parsed as JSON for content stage');
    }

    const caseStudies = extractCaseStudyArray(parsed);
    console.log(`[CaseStudy-Pipeline] Content stage: parsed ${caseStudies.length} case studies`);

    for (let i = 0; i < caseStudies.length && i < this.targetCount; i++) {
      this.mergeIntoCaseStudy(i, caseStudies[i]);
    }
  }

  private async executeKpisSeoStage(): Promise<void> {
    console.log(`[CaseStudy-Pipeline] Executing KPIs/SEO stage for ${this.targetCount} case studies`);
    const partials = this.caseStudyAccumulated.map(cs => ({ ...cs }));
    const promptConfig = buildCaseStudyKpisSeoPrompt(this.inputs, partials);
    const result = await generateWithAI(
      promptConfig.userPrompt,
      promptConfig.systemPrompt,
      promptConfig.maxTokens
    );

    this.lastProvider = result.provider;
    this.lastAiModel = result.model;
    this.totalTokens += result.tokenUsage?.totalTokens ?? 0;
    this.totalInputTokens += result.tokenUsage?.inputTokens ?? 0;
    this.totalOutputTokens += result.tokenUsage?.outputTokens ?? 0;
    if (result.latencyMs) this.totalLatencyMs += result.latencyMs;
    if (result.finishReason) this.lastFinishReason = result.finishReason;
    if (result.keyUsed) this.lastApiKeyMasked = result.keyUsed;

    console.log(`[CaseStudy-Pipeline] KPIs/SEO stage response received. Provider: ${result.provider}, Model: ${result.model}, Content length: ${result.content?.length || 0}`);

    const parsed = parseJsonFromAI(result.content);
    if (!parsed) {
      const extracted = extractFieldsFromRawContent(result.content);
      if (Object.keys(extracted).length > 0) {
        console.warn('[CaseStudy-Pipeline] KPIs/SEO stage: JSON parsing failed, extracted fields as single case study');
        this.mergeSingleIntoFirst(extracted);
        return;
      }
      throw new Error('AI response could not be parsed as JSON for KPIs/SEO stage');
    }

    const caseStudies = extractCaseStudyArray(parsed);
    console.log(`[CaseStudy-Pipeline] KPIs/SEO stage: parsed ${caseStudies.length} case studies`);

    for (let i = 0; i < caseStudies.length && i < this.targetCount; i++) {
      this.mergeIntoCaseStudy(i, caseStudies[i]);
    }
  }

  private async runStageWithRetry(stageName: string, stageFn: () => Promise<void>): Promise<void> {
    const stageStart = Date.now();

    try {
      await stageFn();
      this.stageResults.push({
        stage: stageName,
        success: true,
        provider: this.lastProvider,
        aiModel: this.lastAiModel,
        duration: Date.now() - stageStart,
      });
    } catch (error: any) {
      this.errors.push(`Stage ${stageName} failed: ${error.message}`);
      this.stageResults.push({
        stage: stageName,
        success: false,
        error: error.message,
        duration: Date.now() - stageStart,
      });
      return;
    }

    // Enhancement retry for individual case studies with low confidence
    const lowConfidenceFields = this.getLowConfidenceFields(stageName);
    if (lowConfidenceFields.length > 0) {
      await this.enhanceStage(stageName, lowConfidenceFields);
    }
  }

  // ============================================
  // ENHANCEMENT RETRY
  // ============================================

  private async enhanceStage(stageName: string, lowConfidenceFields: string[]): Promise<void> {
    const enhanceStart = Date.now();

    try {
      // Merge all case study data for enhancement context
      const mergedOutput = this.getMergedOutput();
      const enhancePrompt = buildCaseStudyEnhancementPrompt(stageName, mergedOutput, lowConfidenceFields);

      const result = await generateWithAI(
        enhancePrompt.userPrompt,
        enhancePrompt.systemPrompt,
        enhancePrompt.maxTokens
      );

      this.totalTokens += result.tokenUsage?.totalTokens ?? 0;
      this.totalInputTokens += result.tokenUsage?.inputTokens ?? 0;
      this.totalOutputTokens += result.tokenUsage?.outputTokens ?? 0;
      if (result.latencyMs) this.totalLatencyMs += result.latencyMs;
      if (result.finishReason) this.lastFinishReason = result.finishReason;
      if (result.keyUsed) this.lastApiKeyMasked = result.keyUsed;
      const parsed = parseJsonFromAI(result.content);
      if (parsed) {
        const caseStudies = extractCaseStudyArray(parsed);
        if (caseStudies.length > 0) {
          // Merge enhancement into first case study (most likely to need it)
          this.mergeIntoCaseStudy(0, caseStudies[0]);
        } else {
          this.mergeSingleIntoFirst(parsed);
        }
        this.stageResults.push({
          stage: `${stageName}-enhancement`,
          success: true,
          provider: result.provider,
          aiModel: result.model,
          duration: Date.now() - enhanceStart,
          enhanced: true,
        });
      }
    } catch (error: any) {
      console.warn(`[CaseStudy-Pipeline] Enhancement for ${stageName} failed: ${error.message}`);
    }
  }

  // ============================================
  // RESULT MERGING
  // ============================================

  private mergeIntoCaseStudy(index: number, parsed: Record<string, any>): void {
    for (const [key, value] of Object.entries(parsed)) {
      if (value !== null && value !== undefined) {
        this.caseStudyAccumulated[index][key] = value;
      }
    }
  }

  private mergeSingleIntoFirst(parsed: Record<string, any>): void {
    this.mergeIntoCaseStudy(0, parsed);
  }

  private getMergedOutput(): Record<string, any> {
    // Merge all case studies into a single output for enhancement context
    const merged: Record<string, any> = {};
    for (let i = 0; i < this.caseStudyAccumulated.length; i++) {
      const cs = this.caseStudyAccumulated[i];
      if (Object.keys(cs).length === 0) continue;
      if (this.caseStudyAccumulated.filter(c => Object.keys(c).length > 0).length === 1) {
        // Only one case study, merge directly
        Object.assign(merged, cs);
      } else {
        // Multiple case studies, prefix keys with index
        for (const [key, value] of Object.entries(cs)) {
          merged[`cs${i + 1}_${key}`] = value;
        }
      }
    }
    return merged;
  }

  // ============================================
  // CONFIDENCE ANALYSIS
  // ============================================

  private getLowConfidenceFields(stageName: string): string[] {
    const low: string[] = [];
    const stageCriticalFields: Record<string, string[]> = {
      'case-study-foundation': ['title', 'industry', 'challenge'],
      'case-study-content': ['challenge', 'solution', 'results'],
      'case-study-kpis-seo': ['kpis', 'metaTitle'],
    };

    const criticalFields = stageCriticalFields[stageName] || [];
    // Check across all case studies
    for (const field of criticalFields) {
      const allEmpty = this.caseStudyAccumulated.every(cs => !cs[field] || cs[field] === '');
      if (allEmpty && !low.includes(field)) {
        low.push(field);
      }
    }

    return low;
  }

  private computeOverallConfidence(): number {
    const successCount = this.stageResults.filter(s => s.success && !s.enhanced).length;
    const successRate = successCount / 3;
    return Math.round(successRate * 100);
  }

  private extractFieldConfidences(): Record<string, number> {
    const confidences: Record<string, number> = {};
    const overall = this.computeOverallConfidence();

    const allFields = [
      'title', 'clientName', 'industry',
      'challenge', 'solution', 'results',
      'kpis', 'metaTitle', 'seoKeywords',
    ];

    for (const field of allFields) {
      const anyHas = this.caseStudyAccumulated.some(cs => cs[field]);
      if (anyHas) {
        confidences[field] = overall;
      }
    }

    return confidences;
  }
}