/**
 * Adversarial Twin -- GAN-style clone fidelity testing.
 *
 * Pulls real user messages from memory, generates clone responses
 * to the same contexts, and uses a discriminator to identify which
 * is real. Fidelity score = % of pairs where discriminator was fooled.
 *
 * Style corrections from discriminator feedback are stored in user_model.
 */

import { textOnlySearch } from "./search.ts";

// ── Types ──

export interface TwinTestPair {
  /** Unique pair ID. */
  id: string;
  /** The conversational context the message was responding to. */
  context: string;
  /** The real user message. */
  realMessage: string;
  /** The platform/source of the real message. */
  platform: string;
  /** The clone's generated response (filled in during generation phase). */
  cloneMessage?: string;
  /** Whether the discriminator correctly identified the real message. */
  discriminatorCorrect?: boolean;
  /** The discriminator's reasoning. */
  discriminatorNotes?: string;
  /** Confidence of the discriminator's assessment (0-1). */
  discriminatorConfidence?: number;
}

export interface TwinTestResult {
  pairs: TwinTestPair[];
  /** Fidelity score: % of pairs where discriminator was fooled (0-1). */
  fidelityScore: number;
  /** Specific style corrections extracted from discriminator feedback. */
  corrections: StyleCorrection[];
  timestamp: Date;
}

export interface StyleCorrection {
  aspect: string;
  description: string;
  /** What the clone did wrong. */
  cloneMarker: string;
  /** What the real user did. */
  realMarker: string;
}

// ── Sample Selection ──

/**
 * Pull diverse real user messages from memory for twin testing.
 * Prioritizes exemplar-tagged messages, falls back to general sent messages.
 */
export async function sampleRealMessages(count = 5): Promise<TwinTestPair[]> {
  const pairs: TwinTestPair[] = [];

  // Try exemplar-tagged messages first
  try {
    const exemplarResults = await textOnlySearch(
      "local",
      "representative message",
      count * 2,
      "exemplar",
    );

    for (const result of exemplarResults) {
      if (pairs.length >= count) break;

      const meta = result.metadata as Record<string, unknown>;
      pairs.push({
        id: `twin-${Date.now()}-${pairs.length}`,
        context: (meta.context as string) ?? "general",
        realMessage: result.text,
        platform: (meta.platform as string) ?? "unknown",
      });
    }
  } catch {
    // Exemplar search not available
  }

  // Fall back to sent messages if we don't have enough
  if (pairs.length < count) {
    try {
      const sentResults = await textOnlySearch(
        "local",
        "user conversation message",
        (count - pairs.length) * 3,
      );

      for (const result of sentResults) {
        if (pairs.length >= count) break;

        // Skip very short messages
        if (result.text.length < 30) continue;

        // Skip if we already have this text
        if (pairs.some((p) => p.realMessage === result.text)) continue;

        const meta = result.metadata as Record<string, unknown>;
        pairs.push({
          id: `twin-${Date.now()}-${pairs.length}`,
          context: (meta.context as string) ?? "general",
          realMessage: result.text,
          platform: (meta.platform as string) ?? (meta.source as string) ?? "unknown",
        });
      }
    } catch {
      // Memory search not available
    }
  }

  return pairs;
}

/**
 * Calculate the fidelity score from completed twin test pairs.
 */
export function calculateFidelityScore(pairs: TwinTestPair[]): number {
  const evaluated = pairs.filter((p) => p.discriminatorCorrect !== undefined);
  if (evaluated.length === 0) return 0;

  // Fidelity = % of pairs where discriminator was WRONG (fooled)
  const fooled = evaluated.filter((p) => !p.discriminatorCorrect).length;
  return fooled / evaluated.length;
}

/**
 * Build the discriminator prompt for a single message pair.
 * Messages are presented in randomized order.
 */
export function buildDiscriminatorPrompt(pair: TwinTestPair): { prompt: string; realIsA: boolean } {
  // Randomize which position the real message appears in
  const realIsA = Math.random() < 0.5;

  const messageA = realIsA ? pair.realMessage : pair.cloneMessage!;
  const messageB = realIsA ? pair.cloneMessage! : pair.realMessage;

  const prompt = `You are a writing style discriminator. One of the following two messages was written by a real person, and the other was generated by an AI clone trying to mimic that person's style.

Context: ${pair.context}
Platform: ${pair.platform}

Message A:
"${messageA}"

Message B:
"${messageB}"

Which message is the real person (A or B)? Explain your reasoning by identifying specific style markers:
- Word choice differences
- Sentence structure patterns
- Tone/formality level
- Punctuation and capitalization habits
- Emoji or informal marker usage
- Message length and detail level
- Any "tells" that distinguish human from clone

Respond in JSON:
{"real": "A" or "B", "confidence": 0.0-1.0, "reasoning": "...", "style_markers": [{"aspect": "...", "real_marker": "...", "clone_marker": "..."}]}`;

  return { prompt, realIsA };
}

/**
 * Format a twin test result for display.
 */
export function formatTwinTestResult(result: TwinTestResult): string {
  const lines: string[] = [
    "Twin Test Results",
    "=================",
    "",
    `Fidelity Score: ${(result.fidelityScore * 100).toFixed(0)}% (${result.pairs.filter((p) => !p.discriminatorCorrect).length}/${result.pairs.length} pairs fooled the discriminator)`,
    "",
  ];

  for (let i = 0; i < result.pairs.length; i++) {
    const pair = result.pairs[i]!;
    lines.push(`Pair ${i + 1}: [${pair.context}] (${pair.platform})`);
    lines.push(
      `  Real: "${pair.realMessage.slice(0, 100)}${pair.realMessage.length > 100 ? "..." : ""}"`,
    );
    if (pair.cloneMessage) {
      lines.push(
        `  Clone: "${pair.cloneMessage.slice(0, 100)}${pair.cloneMessage.length > 100 ? "..." : ""}"`,
      );
    }
    if (pair.discriminatorCorrect !== undefined) {
      lines.push(
        `  Result: ${pair.discriminatorCorrect ? "Identified (not fooled)" : "Fooled!"}${pair.discriminatorConfidence ? ` (${(pair.discriminatorConfidence * 100).toFixed(0)}% confidence)` : ""}`,
      );
    }
    if (pair.discriminatorNotes) {
      lines.push(`  Notes: ${pair.discriminatorNotes}`);
    }
    lines.push("");
  }

  if (result.corrections.length > 0) {
    lines.push("Style Corrections:");
    for (const c of result.corrections) {
      lines.push(`- ${c.aspect}: ${c.description}`);
      lines.push(`  Clone: ${c.cloneMarker} | Real: ${c.realMarker}`);
    }
  }

  return lines.join("\n");
}
