import type { ProviderRegistry } from '../providers/registry.js';
import type { JsonRpcResponse } from '../types.js';
import { RESPONSE_FILTER_SYSTEM_PROMPT, REQUEST_OPTIMIZATION_SYSTEM_PROMPT } from './prompts.js';
import { estimateTokens } from './token-counter.js';
import { FilterCache } from './filter-cache.js';
import type { FilterCacheConfig } from './filter-cache.js';
import { FilterMetrics } from './metrics.js';
import type { SystemPromptFetcher } from '../proxymodel/types.js';

export interface LlmProcessorConfig {
  /** Enable request preprocessing */
  enablePreprocessing: boolean;
  /** Enable response filtering */
  enableFiltering: boolean;
  /** Tool name patterns to skip (matched against namespaced name) */
  excludeTools: string[];
  /** Max tokens for LLM calls */
  maxTokens: number;
  /** Token threshold below which responses skip LLM filtering (default 250 tokens ~ 1000 chars) */
  tokenThreshold: number;
  /** Filter cache configuration (optional; omit to use defaults) */
  filterCache?: FilterCacheConfig | undefined;
}

export const DEFAULT_PROCESSOR_CONFIG: LlmProcessorConfig = {
  enablePreprocessing: false,
  enableFiltering: true,
  excludeTools: [],
  maxTokens: 1024,
  tokenThreshold: 250,
};

export interface ProcessedRequest {
  optimized: boolean;
  params: Record<string, unknown>;
}

export interface FilteredResponse {
  filtered: boolean;
  result: unknown;
  originalSize: number;
  filteredSize: number;
}

/**
 * LLM pre-processing pipeline. Intercepts MCP tool calls and uses a local
 * LLM to optimize requests and filter responses, reducing token usage for
 * the upstream Claude model.
 *
 * Includes smart context optimization:
 * - Token-based thresholds to skip filtering small responses
 * - LRU cache for filter decisions on repeated tool calls
 * - Metrics tracking for observability
 */
export class LlmProcessor {
  private readonly filterCache: FilterCache;
  private readonly metrics: FilterMetrics;

  constructor(
    private providers: ProviderRegistry,
    private config: LlmProcessorConfig = DEFAULT_PROCESSOR_CONFIG,
    private readonly getSystemPrompt?: SystemPromptFetcher,
  ) {
    this.filterCache = new FilterCache(config.filterCache);
    this.metrics = new FilterMetrics();
  }

  /** Methods that should never be preprocessed (protocol-level or simple CRUD) */
  private static readonly BYPASS_METHODS = new Set([
    'initialize',
    'tools/list',
    'resources/list',
    'prompts/list',
    'prompts/get',
    'resources/subscribe',
    'resources/unsubscribe',
  ]);

  /** Simple operations that don't benefit from preprocessing */
  private static readonly SIMPLE_OPERATIONS = new Set([
    'create', 'delete', 'remove', 'subscribe', 'unsubscribe',
  ]);

  shouldProcess(method: string, toolName?: string): boolean {
    if (LlmProcessor.BYPASS_METHODS.has(method)) return false;
    if (!toolName) return false;

    // Check exclude list
    if (this.config.excludeTools.some((pattern) => toolName.includes(pattern))) {
      return false;
    }

    // Skip simple CRUD operations
    const baseName = toolName.includes('/') ? toolName.split('/').pop()! : toolName;
    for (const op of LlmProcessor.SIMPLE_OPERATIONS) {
      if (baseName.startsWith(op)) return false;
    }

    return true;
  }

  /**
   * Optimize request parameters using the active LLM provider.
   * Falls back to original params if LLM is unavailable or fails.
   */
  async preprocessRequest(toolName: string, params: Record<string, unknown>): Promise<ProcessedRequest> {
    if (!this.config.enablePreprocessing) {
      return { optimized: false, params };
    }

    const provider = this.providers.getProvider('fast');
    if (!provider) {
      return { optimized: false, params };
    }

    try {
      const systemPrompt = this.getSystemPrompt
        ? await this.getSystemPrompt('llm-request-optimization', REQUEST_OPTIMIZATION_SYSTEM_PROMPT)
        : REQUEST_OPTIMIZATION_SYSTEM_PROMPT;

      const result = await provider.complete({
        messages: [
          { role: 'system', content: systemPrompt },
          { role: 'user', content: `Tool: ${toolName}\nParameters: ${JSON.stringify(params)}` },
        ],
        maxTokens: this.config.maxTokens,
        temperature: 0,
      });

      const optimized = JSON.parse(result.content) as Record<string, unknown>;
      return { optimized: true, params: optimized };
    } catch {
      // LLM failed, fall through to original params
      return { optimized: false, params };
    }
  }

  /**
   * Filter/summarize a tool response using the active LLM provider.
   * Falls back to original response if LLM is unavailable or fails.
   *
   * Uses token-based thresholds and an LRU filter cache to skip unnecessary
   * LLM calls. Records metrics for every filter operation.
   */
  async filterResponse(toolName: string, response: JsonRpcResponse): Promise<FilteredResponse> {
    if (!this.config.enableFiltering) {
      const raw = JSON.stringify(response.result);
      return { filtered: false, result: response.result, originalSize: raw.length, filteredSize: raw.length };
    }

    const provider = this.providers.getProvider('fast');
    if (!provider) {
      const raw = JSON.stringify(response.result);
      return { filtered: false, result: response.result, originalSize: raw.length, filteredSize: raw.length };
    }

    // Don't filter error responses
    if (response.error) {
      return { filtered: false, result: response.result, originalSize: 0, filteredSize: 0 };
    }

    const raw = JSON.stringify(response.result);
    const tokens = estimateTokens(raw);

    // Skip small responses below the token threshold
    if (tokens < this.config.tokenThreshold) {
      return { filtered: false, result: response.result, originalSize: raw.length, filteredSize: raw.length };
    }

    // Check filter cache for a prior decision on this tool
    const cachedDecision = this.filterCache.shouldFilter(toolName);
    if (cachedDecision !== null) {
      this.metrics.recordCacheHit();
      if (!cachedDecision) {
        // Previously decided not to filter this tool's responses
        return { filtered: false, result: response.result, originalSize: raw.length, filteredSize: raw.length };
      }
    } else {
      this.metrics.recordCacheMiss();
    }

    const startTime = performance.now();

    try {
      const systemPrompt = this.getSystemPrompt
        ? await this.getSystemPrompt('llm-response-filter', RESPONSE_FILTER_SYSTEM_PROMPT)
        : RESPONSE_FILTER_SYSTEM_PROMPT;

      const result = await provider.complete({
        messages: [
          { role: 'system', content: systemPrompt },
          { role: 'user', content: `Tool: ${toolName}\nResponse (${raw.length} chars):\n${raw}` },
        ],
        maxTokens: this.config.maxTokens,
        temperature: 0,
      });

      const filtered = JSON.parse(result.content) as unknown;
      const filteredStr = JSON.stringify(filtered);
      const filteredTokens = estimateTokens(filteredStr);
      const latencyMs = performance.now() - startTime;

      this.metrics.recordFilter(tokens, filteredTokens, latencyMs);

      // Cache the decision: if filtering actually saved tokens, remember to filter
      const didSave = filteredStr.length < raw.length;
      this.filterCache.recordDecision(toolName, didSave);

      return {
        filtered: true,
        result: filtered,
        originalSize: raw.length,
        filteredSize: filteredStr.length,
      };
    } catch {
      const latencyMs = performance.now() - startTime;
      this.metrics.recordFilter(tokens, tokens, latencyMs);

      // LLM failed — cache as "don't filter" to avoid repeated failures
      this.filterCache.recordDecision(toolName, false);

      // LLM failed, return original
      return { filtered: false, result: response.result, originalSize: raw.length, filteredSize: raw.length };
    }
  }

  /** Return a snapshot of filter pipeline metrics. */
  getMetrics(): ReturnType<FilterMetrics['getStats']> {
    return this.metrics.getStats();
  }

  /** Reset all metrics. */
  resetMetrics(): void {
    this.metrics.reset();
  }

  /** Clear the filter decision cache. */
  clearFilterCache(): void {
    this.filterCache.clear();
  }
}
