/**
 * Ads AI Pipeline
 *
 * Orchestrates a 3-stage AI pipeline for generating a campaign + ads.
 * Uses company context, ICP data, product info, and brand strategy as seed.
 * Generates 1 campaign with targetCount ads (default 3).
 */

import { generateWithAI } from '../../utils/aiProvider';
import { parseJsonFromAI } from './parseJsonFromAI';
import {
  buildAdsIdentityPrompt,
  buildAdsContentPrompt,
  buildAdsStrategyPrompt,
  buildAdsEnhancementPrompt,
  AdsPipelineInputs,
  PromptResult,
} from './adsPrompts';

// ============================================
// TYPES
// ============================================

export interface AdsPipelineResult {
  campaign: Record<string, any>;
  ads: Record<string, any>[];
  audiences: Record<string, any>[];
  budgets: Record<string, any>[];
  creativeAssets: Record<string, any>[];
  pipelineVersion: string;
  provider: string;
  aiModel: string;
  tokensUsed: number;
  inputTokens: number;
  outputTokens: number;
  processingTimeMs: number;
  latencyMs: number;
  finishReason: string | null;
  apiKeyMasked: string | null;
  overallConfidence: number;
  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 = '1.0';

// ============================================
// JSON PARSING UTILITIES
// ============================================

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;
}

function extractAdArray(parsed: Record<string, any>): Record<string, any>[] {
  if (parsed.ads && Array.isArray(parsed.ads)) return parsed.ads;
  if (parsed.ad && typeof parsed.ad === 'object' && !Array.isArray(parsed.ad)) return [parsed.ad];
  for (const key of Object.keys(parsed)) {
    if (Array.isArray(parsed[key]) && parsed[key].length > 0 && parsed[key][0]?.headline) {
      return parsed[key];
    }
  }
  return [];
}

// ============================================
// PIPELINE CLASS
// ============================================

export class AdsPipeline {
  private inputs: AdsPipelineInputs;
  private campaignAccumulated: Record<string, any>;
  private adsAccumulated: Record<string, any>[];
  private audiencesAccumulated: Record<string, any>[];
  private budgetsAccumulated: Record<string, any>[];
  private creativeAssetsAccumulated: 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 onProgress?: (progress: number, step: string) => void;

  constructor(inputs: AdsPipelineInputs, onProgress?: (progress: number, step: string) => void) {
    this.inputs = inputs;
    this.campaignAccumulated = {};
    this.adsAccumulated = Array.from({ length: inputs.targetCount }, () => ({}));
    this.audiencesAccumulated = [];
    this.budgetsAccumulated = [];
    this.creativeAssetsAccumulated = [];
    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<AdsPipelineResult> {
    // Stage 1: Campaign & Ad Identity
    this.onProgress?.(5, 'Creating campaign identity...');
    await this.runStageWithRetry('ads-identity', () =>
      this.executeStage(
        buildAdsIdentityPrompt(this.inputs),
        'identity',
        0
      )
    );
    this.onProgress?.(35, 'Campaign identity defined');

    // Stage 2: Ad Content & Targeting
    this.onProgress?.(40, 'Generating ad content...');
    await this.runStageWithRetry('ads-content', () =>
      this.executeStage(
        buildAdsContentPrompt(this.inputs, { campaign: this.campaignAccumulated, ads: this.adsAccumulated }),
        'content',
        1
      )
    );
    this.onProgress?.(65, 'Ad content generated');

    // Stage 3: Ad Strategy & Tracking
    this.onProgress?.(70, 'Optimizing ad strategy...');
    await this.runStageWithRetry('ads-strategy', () =>
      this.executeStage(
        buildAdsStrategyPrompt(this.inputs, { campaign: this.campaignAccumulated, ads: this.adsAccumulated }),
        'strategy',
        2
      )
    );
    this.onProgress?.(95, 'Finalizing ads');

    const overallConfidence = this.computeOverallConfidence();

    return {
      campaign: this.campaignAccumulated,
      ads: this.adsAccumulated.filter(ad => ad.name || ad.headline),
      audiences: this.audiencesAccumulated,
      budgets: this.budgetsAccumulated,
      creativeAssets: this.creativeAssetsAccumulated,
      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,
      finishReason: this.lastFinishReason,
      apiKeyMasked: this.lastApiKeyMasked,
      stageResults: this.stageResults,
      errors: this.errors,
    };
  }

  // ============================================
  // STAGE EXECUTION
  // ============================================

  private async executeStage(promptConfig: PromptResult, stageName: string, stageIndex: number): Promise<void> {
    console.log(`[Ads-Pipeline] Executing stage "${stageName}" with ${promptConfig.maxTokens} maxTokens`);

    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(`[Ads-Pipeline] Stage "${stageName}" response received. Provider: ${result.provider}, Content length: ${result.content?.length || 0}`);
    console.log(`[Ads-Pipeline] Stage "${stageName}" raw response (first 500 chars): ${result.content?.substring(0, 500)}`);

    const parsed = parseJsonFromAI(result.content);
    if (!parsed) {
      const extracted = extractFieldsFromRawContent(result.content);
      if (Object.keys(extracted).length > 0) {
        console.warn(`[Ads-Pipeline] Stage "${stageName}": JSON parsing failed, extracted ${Object.keys(extracted).length} fields`);
        this.mergeCampaign(extracted);
        return;
      }
      throw new Error(`AI response could not be parsed as JSON for stage: ${stageName}`);
    }

    // Extract campaign data
    if (parsed.campaign && typeof parsed.campaign === 'object' && !Array.isArray(parsed.campaign)) {
      this.mergeCampaign(parsed.campaign);
    }

    // Extract ads array
    const adsItems = extractAdArray(parsed);
    if (adsItems.length > 0) {
      for (let i = 0; i < adsItems.length && i < this.inputs.targetCount; i++) {
        this.mergeIntoAd(i, adsItems[i]);
      }
    }

    // Extract audiences array
    if (parsed.audiences && Array.isArray(parsed.audiences) && parsed.audiences.length > 0) {
      for (const audience of parsed.audiences) {
        if (audience && typeof audience === 'object' && (audience.name || audience.demographics)) {
          this.mergeIntoAudiences(audience);
        }
      }
    }

    // Extract budgets array
    if (parsed.budgets && Array.isArray(parsed.budgets) && parsed.budgets.length > 0) {
      for (const budget of parsed.budgets) {
        if (budget && typeof budget === 'object' && (budget.dailyBudget || budget.totalBudget)) {
          this.budgetsAccumulated.push({ ...budget });
        }
      }
    }

    // Extract creative assets array
    if (parsed.creativeAssets && Array.isArray(parsed.creativeAssets) && parsed.creativeAssets.length > 0) {
      for (const asset of parsed.creativeAssets) {
        if (asset && typeof asset === 'object' && asset.name) {
          this.creativeAssetsAccumulated.push({ ...asset });
        }
      }
    }
  }

  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;
    }

    const lowFields = this.getLowConfidenceFields(stageName);
    if (lowFields.length > 0) {
      await this.enhanceStage(stageName, lowFields);
    }
  }

  // ============================================
  // ENHANCEMENT RETRY
  // ============================================

  private async enhanceStage(stageName: string, lowConfidenceFields: string[]): Promise<void> {
    const enhanceStart = Date.now();

    try {
      const stageOutput = { campaign: this.campaignAccumulated, ads: this.adsAccumulated, audiences: this.audiencesAccumulated };
      const enhancePrompt = buildAdsEnhancementPrompt(stageName, stageOutput, 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) {
        if (parsed.campaign) this.mergeCampaign(parsed.campaign);
        const adsItems = extractAdArray(parsed);
        if (adsItems.length > 0) {
          for (let i = 0; i < adsItems.length && i < this.inputs.targetCount; i++) {
            this.mergeIntoAd(i, adsItems[i]);
          }
        }
        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(`[Ads-Pipeline] Enhancement for ${stageName} failed: ${error.message}`);
    }
  }

  // ============================================
  // RESULT MERGING
  // ============================================

  private mergeCampaign(parsed: Record<string, any>): void {
    if (!parsed || typeof parsed !== 'object') return;
    for (const [key, value] of Object.entries(parsed)) {
      if (key === 'ads' && Array.isArray(value)) continue; // handled separately
      if (value !== null && value !== undefined) {
        const existing = this.campaignAccumulated[key];
        if (existing === undefined || existing === null || existing === '' || (Array.isArray(existing) && existing.length === 0)) {
          this.campaignAccumulated[key] = value;
        } else if (Array.isArray(value) && value.length > 0 && Array.isArray(existing)) {
          if (value.length > existing.length) {
            this.campaignAccumulated[key] = value;
          }
        }
      }
    }
  }

  private mergeIntoAd(index: number, parsed: Record<string, any>): void {
    if (!parsed || typeof parsed !== 'object') return;
    const target = this.adsAccumulated[index];
    if (!target) return;
    for (const [key, value] of Object.entries(parsed)) {
      if (value !== null && value !== undefined) {
        const existing = target[key];
        if (existing === undefined || existing === null || existing === '' || (Array.isArray(existing) && existing.length === 0)) {
          target[key] = value;
        } else if (typeof value === 'object' && !Array.isArray(value) && typeof existing === 'object' && !Array.isArray(existing)) {
          // Merge nested objects (like targeting, tracking)
          for (const [k, v] of Object.entries(value)) {
            if (v !== null && v !== undefined && (existing[k] === undefined || existing[k] === null)) {
              existing[k] = v;
            }
          }
        } else if (Array.isArray(value) && value.length > 0 && Array.isArray(existing)) {
          if (value.length > existing.length) {
            target[key] = value;
          }
        }
      }
    }
  }

  private mergeIntoAudiences(audience: Record<string, any>): void {
    if (!audience || typeof audience !== 'object') return;
    // Check if we already have an audience with this name — if so, merge into it
    const existingIdx = this.audiencesAccumulated.findIndex(a => a.name && a.name === audience.name);
    if (existingIdx >= 0) {
      const target = this.audiencesAccumulated[existingIdx];
      for (const [key, value] of Object.entries(audience)) {
        if (value !== null && value !== undefined) {
          const existing = target[key];
          if (existing === undefined || existing === null || existing === '' || (Array.isArray(existing) && existing.length === 0)) {
            target[key] = value;
          } else if (typeof value === 'object' && !Array.isArray(value) && typeof existing === 'object' && !Array.isArray(existing)) {
            for (const [k, v] of Object.entries(value)) {
              if (v !== null && v !== undefined && (existing[k] === undefined || existing[k] === null)) {
                existing[k] = v;
              }
            }
          } else if (Array.isArray(value) && value.length > 0 && Array.isArray(existing) && value.length > existing.length) {
            target[key] = value;
          }
        }
      }
    } else {
      this.audiencesAccumulated.push({ ...audience });
    }
  }

  // ============================================
  // CONFIDENCE ANALYSIS
  // ============================================

  private getLowConfidenceFields(stageName: string): string[] {
    const low: string[] = [];
    const stageCriticalFields: Record<string, string[]> = {
      'ads-identity': ['name', 'headline', 'platform'],
      'ads-content': ['description', 'cta'],
      'ads-strategy': ['tracking'],
    };

    const criticalFields = stageCriticalFields[stageName] || [];
    for (const field of criticalFields) {
      // Check how many ads are missing this field
      const missingCount = this.adsAccumulated.filter(ad => !ad[field] || ad[field] === '' || (Array.isArray(ad[field]) && ad[field].length === 0)).length;
      if (missingCount > Math.floor(this.inputs.targetCount / 2)) {
        low.push(field);
      }
    }

    // Also check campaign fields for identity stage
    if (stageName === 'ads-identity') {
      if (!this.campaignAccumulated.name) low.push('campaign.name');
      if (!this.campaignAccumulated.goal) low.push('campaign.goal');
    }

    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);
  }
}