/**
 * Competitor AI Pipeline
 *
 * Orchestrates a 3-stage AI analysis pipeline for competitor generation.
 * Uses company context + ICP data as seed input.
 * Follows the same pattern as ICP/Persona pipelines.
 */

import { generateWithAI } from '../../utils/aiProvider';
import { parseJsonFromAI } from './parseJsonFromAI';
import {
  buildCompetitorBasicPrompt,
  buildCompetitorProductPrompt,
  buildCompetitorStrategyPrompt,
  buildCompetitorEnhancementPrompt,
  CompetitorPipelineInputs,
  PromptResult,
} from './competitorPrompts';
import { researchCompetitor, CompetitorWebResearchData } from './competitorWebResearch';
import { findOfficialWebsite, domainMatchesCompanyName } from './webResearchService';

// ============================================
// TYPES
// ============================================

export interface CompetitorPipelineResult {
  analysis: 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;
  fieldConfidences: Record<string, number>;
  stageResults: StageResult[];
  errors: string[];
  /** Web research data from Stage 0 (null if research was skipped or failed) */
  webResearchData?: CompetitorWebResearchData;
}

export interface StageResult {
  stage: string;
  success: boolean;
  provider?: string;
  aiModel?: string;
  tokensUsed?: number;
  error?: string;
  duration: number;
  enhanced?: boolean;
}

const PIPELINE_VERSION = '1.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;
}

// ============================================
// PIPELINE CLASS
// ============================================

export class CompetitorPipeline {
  private inputs: CompetitorPipelineInputs;
  private accumulated: 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 webResearchData: CompetitorWebResearchData | null;

  constructor(inputs: CompetitorPipelineInputs) {
    this.inputs = inputs;
    this.accumulated = {};
    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.webResearchData = null;
  }

  async run(): Promise<CompetitorPipelineResult> {
    // Stage 0: Web Research (non-blocking — pipeline continues even if research fails)
    await this.runWebResearchStage();

    // Stage 1: Basic Profile & Market Position
    await this.runStageWithRetry('basic', () =>
      this.executeStage(buildCompetitorBasicPrompt(this.inputs), 'name')
    );

    // The competitor's identity is only known once Stage 1 names it, so the
    // website is resolved here rather than trusted from the model.
    await this.resolveOfficialWebsite();

    // Stage 2: Product Analysis & Value Proposition
    await this.runStageWithRetry('product', () =>
      this.executeStage(buildCompetitorProductPrompt(this.inputs, this.accumulated), 'primaryProduct')
    );

    // Stage 3: Marketing, SWOT & Strategic Response
    await this.runStageWithRetry('strategy', () =>
      this.executeStage(buildCompetitorStrategyPrompt(this.inputs, this.accumulated), 'recommendedStrategy')
    );

    const overallConfidence = this.computeOverallConfidence();
    const fieldConfidences = this.extractFieldConfidences();

    return {
      analysis: this.accumulated,
      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,
      finishReason: this.lastFinishReason,
      apiKeyMasked: this.lastApiKeyMasked,
      overallConfidence,
      fieldConfidences,
      stageResults: this.stageResults,
      errors: this.errors,
      webResearchData: this.webResearchData || undefined,
    };
  }

  // ============================================
  // STAGE 0: WEB RESEARCH (non-blocking)
  // ============================================

  private async runWebResearchStage(): Promise<void> {
    const stageStart = Date.now();
    console.log(`[Competitor-Pipeline] Starting Stage 0: Web Research`);

    try {
      this.webResearchData = await researchCompetitor(this.inputs);

      // Attach research data to inputs so prompts can reference it
      if (this.webResearchData) {
        this.inputs = { ...this.inputs, webResearchData: this.webResearchData };
        console.log(`[Competitor-Pipeline] Web research complete. Website: ${this.webResearchData.detectedWebsite || 'none'}`);
      }

      this.stageResults.push({
        stage: 'web-research',
        success: true,
        duration: Date.now() - stageStart,
      });
    } catch (error: any) {
      console.warn(`[Competitor-Pipeline] Web research failed: ${error.message}`);
      this.errors.push(`Web research skipped: ${error.message}`);
      this.stageResults.push({
        stage: 'web-research',
        success: false,
        error: error.message,
        duration: Date.now() - stageStart,
      });
      // Non-blocking: pipeline continues without web research
    }
  }

  // ============================================
  // OFFICIAL WEBSITE RESOLUTION
  // ============================================

  /**
   * Settle the competitor's website from web search rather than from the model.
   *
   * A URL is a fact, and a model asked for one will produce a plausible domain
   * that often doesn't exist — which is why generated competitors had wrong
   * websites. Order of preference:
   *   1. Stage 0's researched site, but only if its domain matches this
   *      competitor's name (in Auto-Fill the research ran before the name
   *      existed, so it may belong to an entirely different company).
   *   2. A targeted "<name> official website" search.
   *   3. Whatever the model produced, left untouched as a last resort.
   *
   * Non-blocking: a lookup failure leaves the model's value in place.
   */
  private async resolveOfficialWebsite(): Promise<void> {
    const name = typeof this.accumulated.name === 'string' ? this.accumulated.name.trim() : '';
    if (!name) return;

    const researched = this.webResearchData?.detectedWebsite || null;
    if (researched && domainMatchesCompanyName(researched, name)) {
      if (this.accumulated.website !== researched) {
        console.log(`[Competitor-Pipeline] Website from research: ${researched} (was "${this.accumulated.website || 'none'}")`);
      }
      this.accumulated.website = researched;
      return;
    }

    try {
      const found = await findOfficialWebsite(name);
      if (found) {
        console.log(`[Competitor-Pipeline] Website from targeted search for "${name}": ${found} (was "${this.accumulated.website || 'none'}")`);
        this.accumulated.website = found;
        // Keep the research metadata consistent with what we actually stored.
        if (this.webResearchData) this.webResearchData.detectedWebsite = found;
      } else {
        console.log(`[Competitor-Pipeline] No official website found for "${name}" — keeping AI value "${this.accumulated.website || 'none'}"`);
      }
    } catch (error: any) {
      console.warn(`[Competitor-Pipeline] Website resolution failed for "${name}": ${error.message}`);
    }
  }

  // ============================================
  // STAGE EXECUTION
  // ============================================

  private async executeStage(promptConfig: PromptResult, primaryField: string): Promise<void> {
    console.log(`[Competitor-Pipeline] Executing stage "${primaryField}" 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(`[Competitor-Pipeline] Stage "${primaryField}" response received. Provider: ${result.provider}, Model: ${result.model}, Content length: ${result.content?.length || 0}`);
    console.log(`[Competitor-Pipeline] Stage "${primaryField}" 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(`[Competitor-Pipeline] Stage "${primaryField}": JSON parsing failed, extracted ${Object.keys(extracted).length} fields`);
        this.mergeStageResult(extracted);
        return;
      }
      throw new Error(`AI response could not be parsed as JSON for stage: ${primaryField}`);
    }

    console.log(`[Competitor-Pipeline] Stage "${primaryField}" parsed keys: ${Object.keys(parsed).join(', ')}`);
    this.mergeStageResult(parsed);
  }

  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 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 {
      const stageOutput = this.getStageOutput(stageName);
      const enhancePrompt = buildCompetitorEnhancementPrompt(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) {
        this.mergeStageResult(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(`[Competitor-Pipeline] Enhancement for ${stageName} failed: ${error.message}`);
    }
  }

  // ============================================
  // RESULT MERGING
  // ============================================

  private mergeStageResult(parsed: Record<string, any>): void {
    for (const [key, value] of Object.entries(parsed)) {
      if (value !== null && value !== undefined) {
        this.accumulated[key] = value;
      }
    }
  }

  // ============================================
  // CONFIDENCE ANALYSIS
  // ============================================

  private getLowConfidenceFields(stageName: string): string[] {
    const low: string[] = [];
    const stageCriticalFields: Record<string, string[]> = {
      'basic': ['name', 'competitorType', 'marketPosition'],
      'product': ['primaryProduct', 'valueProposition'],
      'strategy': ['strengths', 'ourAdvantages', 'recommendedStrategy'],
    };

    const criticalFields = stageCriticalFields[stageName] || [];
    for (const field of criticalFields) {
      if (!this.accumulated[field] || this.accumulated[field] === '') {
        if (!low.includes(field)) {
          low.push(field);
        }
      }
    }

    return low;
  }

  private getStageOutput(stageName: string): Record<string, any> {
    const stageFieldMap: Record<string, string[]> = {
      'basic': ['name', 'website', 'headquarters', 'foundedYear', 'companySize', 'fundingStage', 'fundingRaised', 'employeeCount', 'revenueEstimate', 'competitorType', 'threatLevel', 'marketPosition', 'marketShare', 'geographicReach', 'targetAudience', 'industriesServed', 'isActive'],
      'product': ['primaryProduct', 'productCategories', 'keyFeatures', 'pricingStrategy', 'pricingDetails', 'freeTrial', 'demoAvailable', 'valueProposition', 'tagline', 'messaging', 'differentiators'],
      'strategy': ['marketingChannels', 'contentStrategy', 'seoKeywords', 'adSpendEstimate', 'strengths', 'weaknesses', 'opportunities', 'threats', 'swotSummary', 'ourAdvantages', 'ourVulnerabilities', 'recommendedStrategy', 'battlecards'],
    };

    const fields = stageFieldMap[stageName] || [];
    const output: Record<string, any> = {};
    for (const field of fields) {
      if (this.accumulated[field] !== undefined) {
        output[field] = this.accumulated[field];
      }
    }
    return output;
  }

  private computeOverallConfidence(): number {
    // Confidence is the share of the AI stages that succeeded, so only those
    // count. Stage 0 (web research) is a non-AI, non-blocking step and used to
    // be counted in the numerator against a hardcoded divisor of 3 — when it
    // succeeded the result was 4/3 = 133, which exceeds the AiContext schema's
    // max of 100 and made the save throw, failing the whole generation after
    // every AI stage had already succeeded.
    const aiStageResults = this.stageResults.filter(
      (s) => s.stage !== 'web-research' && !s.enhanced,
    );
    if (aiStageResults.length === 0) return 0;

    const successCount = aiStageResults.filter((s) => s.success).length;
    const successRate = successCount / aiStageResults.length;
    // Clamped so a future stage change can never again produce a value the
    // model rejects.
    return Math.min(100, Math.max(0, Math.round(successRate * 100)));
  }

  private extractFieldConfidences(): Record<string, number> {
    const confidences: Record<string, number> = {};
    const overall = this.computeOverallConfidence();

    const allFields = [
      'name', 'website', 'competitorType', 'threatLevel', 'marketPosition', 'marketShare',
      'primaryProduct', 'pricingStrategy', 'valueProposition', 'differentiators',
      'strengths', 'weaknesses', 'ourAdvantages', 'recommendedStrategy',
    ];

    for (const field of allFields) {
      if (this.accumulated[field]) {
        confidences[field] = overall;
      }
    }

    return confidences;
  }
}