/**
 * Marketing Calendar & Timing — AI Pipeline
 *
 * 3-Stage Pipeline:
 *   Stage 1 — Seasonal calendar structure & timing recommendations
 *   Stage 2 — Budget allocation per channel / quarter
 *   Stage 3 — Specific event ideas & content themes
 */

import { generateWithAI } from '../../utils/aiProvider';
import { parseJsonFromAI } from './parseJsonFromAI';
import {
  buildMarketingCalendarSeasonalPrompt,
  buildMarketingCalendarBudgetPrompt,
  buildMarketingCalendarEventsPrompt,
  MarketingCalendarInputs,
} from './marketingCalendarPrompts';

// ─── Result Types ──────────────────────────────────────────────

export interface StageResult {
  stage: string;
  success: boolean;
  provider?: string;
  aiModel?: string;
  tokensUsed?: number;
  error?: string;
  duration: number;
  enhanced?: boolean;
}

export interface MarketingCalendarPipelineResult {
  seasonalPlan: Record<string, any> | null;
  timingRecommendations: Record<string, any>[];
  budgetPlan: Record<string, any> | null;
  calendarEvents: Record<string, any>[];
  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[];
}

// ─── Pipeline ─────────────────────────────────────────────────

/**
 * Which AI stages to execute. Each stage is a separate (expensive) AI request,
 * so callers should request only the stages whose output they actually consume:
 *   - 'seasonal' → seasonalPlan + timingRecommendations
 *   - 'budget'   → budgetPlan   + calendarEvents
 *   - 'events'   → calendarEvents (detailed events)
 * Skipping unused stages removes wasted AI calls (the dominant latency).
 */
export type MarketingCalendarStage = 'seasonal' | 'budget' | 'events';

const ALL_STAGES: MarketingCalendarStage[] = ['seasonal', 'budget', 'events'];

export class MarketingCalendarPipeline {
  private inputs: MarketingCalendarInputs;
  private onProgress?: (percentage: number, step: string) => void;
  private stages: MarketingCalendarStage[];

  // Accumulated results
  private seasonalPlan: Record<string, any> | null = null;
  private timingRecommendations: Record<string, any>[] = [];
  private budgetPlan: Record<string, any> | null = null;
  private calendarEvents: Record<string, any>[] = [];

  // Metadata
  private totalTokens = 0;
  private totalInputTokens = 0;
  private totalOutputTokens = 0;
  private provider = '';
  private aiModel = '';
  private finishReason: string | null = null;
  private apiKeyMasked: string | null = null;
  private stageResults: StageResult[] = [];
  private errors: string[] = [];
  private startTime = Date.now();

  constructor(
    inputs: MarketingCalendarInputs,
    onProgress?: (percentage: number, step: string) => void,
    stages: MarketingCalendarStage[] = ALL_STAGES,
  ) {
    this.inputs = inputs;
    this.onProgress = onProgress;
    // Preserve canonical order (seasonal → budget → events) so later stages can
    // still consume earlier stages' output as context when both are requested.
    this.stages = ALL_STAGES.filter(s => stages.includes(s));
  }

  async run(): Promise<MarketingCalendarPipelineResult> {
    // Only run the requested stages. Progress is divided across the ACTIVE stage
    // count so a single-stage run still reports 33→100 correctly.
    const totalStages = this.stages.length || 1;
    let done = 0;

    // Stage 1: Seasonal Calendar Structure & Timing
    if (this.stages.includes('seasonal')) {
      await this.runStageWithRetry('seasonal-calendar', async () => {
        await this.executeStage(
          buildMarketingCalendarSeasonalPrompt(this.inputs),
          'seasonal-calendar',
          1
        );
      });
      done++;
      this.onProgress?.(Math.round((done / totalStages) * 100), 'Seasonal calendar generated');
    }

    // Stage 2: Budget Allocation (uses the seasonal plan as context when available)
    if (this.stages.includes('budget')) {
      await this.runStageWithRetry('budget-allocation', async () => {
        await this.executeStage(
          buildMarketingCalendarBudgetPrompt(this.inputs, this.seasonalPlan ? { seasonalPlan: this.seasonalPlan } : undefined),
          'budget-allocation',
          2
        );
      });
      done++;
      this.onProgress?.(Math.round((done / totalStages) * 100), 'Budget allocation generated');
    }

    // Stage 3: Specific Calendar Events (uses the budget plan as context when available)
    if (this.stages.includes('events')) {
      await this.runStageWithRetry('calendar-events', async () => {
        await this.executeStage(
          buildMarketingCalendarEventsPrompt(
            this.inputs,
            this.budgetPlan ? { budgetPlan: this.budgetPlan } : undefined
          ),
          'calendar-events',
          3
        );
      });
      done++;
      this.onProgress?.(Math.round((done / totalStages) * 100), 'Calendar events generated');
    }

    if (this.onProgress) this.onProgress(100, 'Generation complete');

    return this.buildResult();
  }

  private async executeStage(promptConfig: { systemPrompt: string; userPrompt: string; maxTokens: number }, stageName: string, _stageIndex: number): Promise<void> {
    const stageStart = Date.now();

    const result = await generateWithAI(promptConfig.userPrompt, promptConfig.systemPrompt, promptConfig.maxTokens);
    const parsed = parseJsonFromAI(result.content);

    if (!parsed) {
      throw new Error(`Failed to parse AI response for stage: ${stageName}`);
    }

    // Accumulate tokens
    this.totalTokens += result.tokenUsage?.totalTokens ?? 0;
    this.totalInputTokens += result.tokenUsage?.inputTokens ?? 0;
    this.totalOutputTokens += result.tokenUsage?.outputTokens ?? 0;
    this.provider = result.provider || this.provider;
    this.aiModel = result.model || this.aiModel;
    this.finishReason = result.finishReason || this.finishReason;
    this.apiKeyMasked = result.keyUsed || this.apiKeyMasked;

    // Merge results based on stage
    this.mergeIntoResult(stageName, parsed);

    const duration = Date.now() - stageStart;
    this.stageResults.push({
      stage: stageName,
      success: true,
      provider: result.provider,
      aiModel: result.model,
      tokensUsed: result.tokenUsage?.totalTokens ?? 0,
      duration,
      enhanced: false,
    });
  }

  private async runStageWithRetry(stageName: string, stageFn: () => Promise<void>): Promise<void> {
    const stageStart = Date.now();
    try {
      await stageFn();
    } catch (error: any) {
      const duration = Date.now() - stageStart;
      this.errors.push(`Stage "${stageName}" failed: ${error.message}`);
      this.stageResults.push({
        stage: stageName,
        success: false,
        error: error.message,
        duration,
      });
    }
  }

  private mergeIntoResult(stageName: string, parsed: Record<string, any>): void {
    switch (stageName) {
      case 'seasonal-calendar':
        if (parsed.seasonalPlan) this.seasonalPlan = parsed.seasonalPlan;
        if (Array.isArray(parsed.timingRecommendations)) {
          this.timingRecommendations = parsed.timingRecommendations;
        }
        break;
      case 'budget-allocation':
        if (parsed.budgetPlan) this.budgetPlan = parsed.budgetPlan;
        if (Array.isArray(parsed.calendarEvents)) {
          // Merge with existing events or start new list
          this.calendarEvents = [...this.calendarEvents, ...parsed.calendarEvents];
        }
        break;
      case 'calendar-events':
        if (Array.isArray(parsed.events)) {
          this.calendarEvents = [...this.calendarEvents, ...parsed.events];
        }
        break;
    }
  }

  private computeOverallConfidence(): number {
    if (this.stageResults.length === 0) return 0;
    const successful = this.stageResults.filter(s => s.success).length;
    return (successful / this.stageResults.length) * 100;
  }

  private buildResult(): MarketingCalendarPipelineResult {
    const processingTimeMs = Date.now() - this.startTime;
    return {
      seasonalPlan: this.seasonalPlan,
      timingRecommendations: this.timingRecommendations,
      budgetPlan: this.budgetPlan,
      calendarEvents: this.calendarEvents,
      pipelineVersion: '1.0.0',
      provider: this.provider,
      aiModel: this.aiModel,
      tokensUsed: this.totalTokens,
      inputTokens: this.totalInputTokens,
      outputTokens: this.totalOutputTokens,
      processingTimeMs,
      latencyMs: processingTimeMs,
      overallConfidence: this.computeOverallConfidence(),
      finishReason: this.finishReason,
      apiKeyMasked: this.apiKeyMasked,
      stageResults: this.stageResults,
      errors: this.errors,
    };
  }
}