import type { RegistryServer, SearchOptions, RegistryClientConfig, RegistryName } from './types.js';
import { RegistrySource } from './base.js';
import { OfficialRegistrySource } from './sources/official.js';
import { GlamaRegistrySource } from './sources/glama.js';
import { SmitheryRegistrySource } from './sources/smithery.js';
import { RegistryCache } from './cache.js';
import { deduplicateResults } from './dedup.js';
import { rankResults } from './ranking.js';

export class RegistryClient {
  private sources: Map<RegistryName, RegistrySource>;
  private cache: RegistryCache;
  private enabledRegistries: RegistryName[];
  private metrics = {
    queryLatencies: new Map<string, number[]>(),
    errorCounts: new Map<string, number>(),
  };

  constructor(config: RegistryClientConfig = {}) {
    this.enabledRegistries = config.registries ?? ['official', 'glama', 'smithery'];
    this.cache = new RegistryCache(config.cacheTTLMs);

    this.sources = new Map<RegistryName, RegistrySource>([
      ['official', new OfficialRegistrySource()],
      ['glama', new GlamaRegistrySource()],
      ['smithery', new SmitheryRegistrySource()],
    ]);
  }

  async search(options: SearchOptions): Promise<RegistryServer[]> {
    // Check cache
    const cached = this.cache.get(options.query, options);
    if (cached !== null) {
      return cached;
    }

    const registries = options.registries ?? this.enabledRegistries;
    const limit = options.limit ?? 20;

    // Query all enabled registries in parallel
    const promises = registries
      .map((name) => this.sources.get(name))
      .filter((source): source is RegistrySource => source !== undefined)
      .map(async (source) => {
        const start = Date.now();
        try {
          const results = await source.search(options.query, limit);
          this.recordLatency(source.name, Date.now() - start);
          return results;
        } catch (error) {
          this.recordError(source.name);
          // Graceful degradation: log and continue
          return [];
        }
      });

    const settled = await Promise.all(promises);
    let combined = settled.flat();

    // Apply filters
    if (options.verified === true) {
      combined = combined.filter((s) => s.verified);
    }
    if (options.transport !== undefined) {
      combined = combined.filter((s) => s.transport === options.transport);
    }

    // Deduplicate, rank, and limit
    const deduped = deduplicateResults(combined);
    const ranked = rankResults(deduped, options.query);
    const results = ranked.slice(0, limit);

    // Cache results
    this.cache.set(options.query, options, results);

    return results;
  }

  getCacheMetrics(): { hits: number; misses: number; ratio: number } {
    return this.cache.getHitRatio();
  }

  getQueryLatencies(): Map<string, number[]> {
    return new Map(this.metrics.queryLatencies);
  }

  getErrorCounts(): Map<string, number> {
    return new Map(this.metrics.errorCounts);
  }

  clearCache(): void {
    this.cache.clear();
  }

  private recordLatency(source: string, ms: number): void {
    const existing = this.metrics.queryLatencies.get(source) ?? [];
    existing.push(ms);
    this.metrics.queryLatencies.set(source, existing);
  }

  private recordError(source: string): void {
    const count = this.metrics.errorCounts.get(source) ?? 0;
    this.metrics.errorCounts.set(source, count + 1);
  }
}
