import type { SupportedProvider } from "../index";
import AnthropicMessagesInteraction from "./llmProviders/anthropic";
import AzureChatCompletionInteraction from "./llmProviders/azure";
import AzureResponsesInteraction from "./llmProviders/azure-responses";
import BedrockConverseInteraction from "./llmProviders/bedrock";
import CerebrasChatCompletionInteraction from "./llmProviders/cerebras";
import CohereChatInteraction from "./llmProviders/cohere";
import type {
  DualLlmAnalysis,
  Interaction,
  InteractionUtils,
} from "./llmProviders/common";
import DeepSeekChatCompletionInteraction from "./llmProviders/deepseek";
import GeminiGenerateContentInteraction from "./llmProviders/gemini";
import GroqChatCompletionInteraction from "./llmProviders/groq";
import MinimaxChatCompletionInteraction from "./llmProviders/minimax";
import MistralChatCompletionInteraction from "./llmProviders/mistral";
import OllamaChatCompletionInteraction from "./llmProviders/ollama";
import OpenAiChatCompletionInteraction from "./llmProviders/openai";
import OpenAiEmbeddingInteraction from "./llmProviders/openai-embedding";
import OpenAiResponsesInteraction from "./llmProviders/openai-responses";
import OpenrouterChatCompletionInteraction from "./llmProviders/openrouter";
import PerplexityChatCompletionInteraction from "./llmProviders/perplexity";
import VllmChatCompletionInteraction from "./llmProviders/vllm";
import XaiChatCompletionInteraction from "./llmProviders/xai";
import ZhipuaiChatCompletionInteraction from "./llmProviders/zhipuai";
import type { PartialUIMessage } from "./types";

type InteractionFactory = (interaction: Interaction) => InteractionUtils;

const interactionFactories: Record<Interaction["type"], InteractionFactory> = {
  "openai:chatCompletions": (i) => new OpenAiChatCompletionInteraction(i),
  "openai:responses": (i) => new OpenAiResponsesInteraction(i),
  "openai:embeddings": (i) => new OpenAiEmbeddingInteraction(i),
  "openrouter:chatCompletions": (i) =>
    new OpenrouterChatCompletionInteraction(i),
  "anthropic:messages": (i) => new AnthropicMessagesInteraction(i),
  "bedrock:converse": (i) => new BedrockConverseInteraction(i),
  "cerebras:chatCompletions": (i) => new CerebrasChatCompletionInteraction(i),
  "cohere:chat": (i) => new CohereChatInteraction(i),
  "gemini:generateContent": (i) => new GeminiGenerateContentInteraction(i),
  "mistral:chatCompletions": (i) => new MistralChatCompletionInteraction(i),
  "ollama:chatCompletions": (i) => new OllamaChatCompletionInteraction(i),
  "perplexity:chatCompletions": (i) =>
    new PerplexityChatCompletionInteraction(i),
  "vllm:chatCompletions": (i) => new VllmChatCompletionInteraction(i),
  "zhipuai:chatCompletions": (i) => new ZhipuaiChatCompletionInteraction(i),
  "deepseek:chatCompletions": (i) => new DeepSeekChatCompletionInteraction(i),
  "groq:chatCompletions": (i) => new GroqChatCompletionInteraction(i),
  "xai:chatCompletions": (i) => new XaiChatCompletionInteraction(i),
  "minimax:chatCompletions": (i) => new MinimaxChatCompletionInteraction(i),
  "azure:chatCompletions": (i) => new AzureChatCompletionInteraction(i),
  "azure:responses": (i) => new AzureResponsesInteraction(i),
};

export interface CostSavingsInput {
  cost: string | null | undefined;
  baselineCost: string | null | undefined;
  toonCostSavings: string | null | undefined;
  toonTokensBefore: number | null | undefined;
  toonTokensAfter: number | null | undefined;
}

export interface CostSavingsResult {
  /** Savings from model optimization (baselineCost - cost) */
  costOptimizationSavings: number;
  /** Savings from TOON compression */
  toonSavings: number;
  /** Number of tokens saved by TOON compression */
  toonTokensSaved: number | null;
  /** Total savings (costOptimization + toon) */
  totalSavings: number;
  /** Baseline cost before any optimization */
  baselineCost: number;
  /** Actual cost after optimization */
  actualCost: number;
  /** Total savings as percentage of baseline */
  savingsPercent: number;
  /** Whether there are any savings at all */
  hasSavings: boolean;
}

/**
 * Calculate all cost savings from an interaction.
 * Used by both the logs table and detail view for consistent display.
 */
export function calculateCostSavings(
  input: CostSavingsInput,
): CostSavingsResult {
  const costNum = input.cost ? Number.parseFloat(input.cost) : 0;
  const baselineCostNum = input.baselineCost
    ? Number.parseFloat(input.baselineCost)
    : 0;
  const toonCostSavingsNum = input.toonCostSavings
    ? Number.parseFloat(input.toonCostSavings)
    : 0;

  // Calculate tokens saved from TOON compression
  const toonTokensSaved =
    input.toonTokensBefore &&
    input.toonTokensAfter &&
    input.toonTokensBefore > input.toonTokensAfter
      ? input.toonTokensBefore - input.toonTokensAfter
      : null;

  // Calculate cost optimization savings (from model selection)
  const costOptimizationSavings = baselineCostNum - costNum;

  // Calculate total savings
  const totalSavings = costOptimizationSavings + toonCostSavingsNum;

  // Calculate savings percentage
  const savingsPercent =
    baselineCostNum > 0 ? (totalSavings / baselineCostNum) * 100 : 0;

  return {
    costOptimizationSavings,
    toonSavings: toonCostSavingsNum,
    toonTokensSaved,
    totalSavings,
    baselineCost: baselineCostNum,
    actualCost: baselineCostNum - totalSavings,
    savingsPercent,
    hasSavings: totalSavings !== 0,
  };
}

export class DynamicInteraction implements InteractionUtils {
  private interactionClass: InteractionUtils;
  private interaction: Interaction;

  id: string;
  profileId: string | null;
  externalAgentId: string | null;
  executionId: string | null;
  unsafeContextBoundary: Interaction["unsafeContextBoundary"];
  type: Interaction["type"];
  provider: SupportedProvider;
  endpoint: string;
  createdAt: string;
  modelName: string;

  constructor(interaction: Interaction) {
    const [provider, endpoint] = interaction.type.split(":");

    this.interaction = interaction;
    this.id = interaction.id;
    this.profileId = interaction.profileId;
    this.externalAgentId = interaction.externalAgentId;
    this.executionId = interaction.executionId;
    this.unsafeContextBoundary = interaction.unsafeContextBoundary;
    this.type = interaction.type;
    this.provider = provider as SupportedProvider;
    this.endpoint = endpoint;
    this.createdAt = interaction.createdAt;

    this.interactionClass = this.getInteractionClass(interaction);

    this.modelName = this.interactionClass.modelName;
  }

  private getInteractionClass(interaction: Interaction): InteractionUtils {
    const factory =
      interactionFactories[this.type as keyof typeof interactionFactories];
    if (!factory) {
      throw new Error(`Unsupported interaction type: ${this.type}`);
    }
    return factory(interaction);
  }

  isLastMessageToolCall(): boolean {
    return this.interactionClass.isLastMessageToolCall();
  }

  getLastToolCallId(): string | null {
    return this.interactionClass.getLastToolCallId();
  }

  getToolNamesRefused(): string[] {
    return this.interactionClass.getToolNamesRefused();
  }

  getToolNamesRequested(): string[] {
    return this.interactionClass.getToolNamesRequested();
  }

  getToolNamesUsed(): string[] {
    return this.interactionClass.getToolNamesUsed();
  }

  getToolRefusedCount(): number {
    return this.interactionClass.getToolRefusedCount();
  }

  getLastUserMessage(): string {
    return this.interactionClass.getLastUserMessage();
  }

  getLastAssistantResponse(): string {
    return this.interactionClass.getLastAssistantResponse();
  }

  /**
   * Map request messages, combining tool calls with their results and dual LLM analysis
   */
  mapToUiMessages(dualLlmAnalyses?: DualLlmAnalysis[]): PartialUIMessage[] {
    return this.interactionClass.mapToUiMessages(dualLlmAnalyses);
  }

  /**
   * Get TOON compression savings from database-stored token counts
   * Returns null if no TOON compression data available
   */
  getToonSavings(): {
    originalSize: number;
    compressedSize: number;
    savedCharacters: number;
    percentageSaved: number;
  } | null {
    const toonTokensBefore = this.interaction.toonTokensBefore;
    const toonTokensAfter = this.interaction.toonTokensAfter;

    // Return null if no TOON compression data
    if (
      toonTokensBefore === null ||
      toonTokensAfter === null ||
      toonTokensBefore === undefined ||
      toonTokensAfter === undefined
    ) {
      return null;
    }

    // Only show savings if there was actual compression
    if (toonTokensAfter >= toonTokensBefore || toonTokensBefore === 0) {
      return null;
    }

    const savedCharacters = toonTokensBefore - toonTokensAfter;
    const percentageSaved = (savedCharacters / toonTokensBefore) * 100;

    return {
      originalSize: toonTokensBefore,
      compressedSize: toonTokensAfter,
      savedCharacters,
      percentageSaved,
    };
  }
}
