import { MODEL_PROVIDERS, type Config, type ModelConfig, type ModelProvider } from '../config/index.js';

const SUPPORTED_PROVIDER_APIS = new Set<ModelProvider>(['openai', 'anthropic', 'gemini', 'openrouter', 'xai']);
const CACHE_TTL_MS = 5 * 60_000;
const REQUEST_TIMEOUT_MS = 10_000;

export interface ProviderModelCatalog {
  provider: ModelProvider;
  models: string[];
  source: 'api' | 'config' | 'unavailable';
  error?: string;
  fetchedAt: number;
}

export interface ModelCatalogFetcher {
  (opts?: { provider?: string; forceRefresh?: boolean }): Promise<ProviderModelCatalog[]>;
}

interface CacheEntry {
  expiresAt: number;
  value: ProviderModelCatalog;
}

function uniqueSorted(values: string[]): string[] {
  return Array.from(new Set(values.filter(Boolean))).sort((a, b) => a.localeCompare(b));
}

function getProviderConfigMap(config: Config): Partial<Record<ModelProvider, ModelConfig>> {
  const providerConfigs: Partial<Record<ModelProvider, ModelConfig>> = {};
  const modelConfigs: ModelConfig[] = [
    config.models.default,
    ...(config.models.fast ? [config.models.fast] : []),
    ...(config.models.complex ? [config.models.complex] : []),
    ...(config.models.local ? [config.models.local] : []),
    ...Object.values(config.models.local_providers ?? {}),
  ];

  for (const modelConfig of modelConfigs) {
    providerConfigs[modelConfig.provider] = modelConfig;
    if (modelConfig.fallback) {
      providerConfigs[modelConfig.fallback.provider] = modelConfig.fallback;
    }
  }
  return providerConfigs;
}

function getConfiguredModelsForProvider(config: Config, provider: ModelProvider): string[] {
  const values: string[] = [];
  const all = [
    config.models.default,
    ...(config.models.fast ? [config.models.fast] : []),
    ...(config.models.complex ? [config.models.complex] : []),
    ...(config.models.local ? [config.models.local] : []),
    ...Object.values(config.models.local_providers ?? {}),
  ];
  for (const entry of all) {
    if (entry.provider === provider) {
      values.push(entry.model);
    }
    if (entry.fallback?.provider === provider) {
      values.push(entry.fallback.model);
    }
  }
  return uniqueSorted(values);
}

function envVarForProvider(provider: ModelProvider): string | undefined {
  if (provider === 'openai') {return 'OPENAI_API_KEY';}
  if (provider === 'anthropic') {return 'ANTHROPIC_API_KEY';}
  if (provider === 'gemini') {return 'GEMINI_API_KEY';}
  if (provider === 'openrouter') {return 'OPENROUTER_API_KEY';}
  if (provider === 'xai') {return 'XAI_API_KEY';}
  return undefined;
}

function resolveApiKey(provider: ModelProvider, cfg?: ModelConfig): string | undefined {
  return cfg?.api_key ?? (envVarForProvider(provider) ? process.env[envVarForProvider(provider)!] : undefined);
}

async function fetchJson(url: string, headers?: Record<string, string>): Promise<unknown> {
  const response = await fetch(url, {
    method: 'GET',
    headers,
    signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
  });
  if (!response.ok) {
    throw new Error(`${response.status} ${response.statusText}`);
  }
  return response.json();
}

async function fetchProviderModels(provider: ModelProvider, cfg?: ModelConfig): Promise<string[]> {
  if (provider === 'openai') {
    const apiKey = resolveApiKey(provider, cfg);
    if (!apiKey) {throw new Error('Missing API key');}
    const base = cfg?.endpoint ?? 'https://api.openai.com/v1';
    const json = await fetchJson(`${base.replace(/\/$/, '')}/models`, { Authorization: `Bearer ${apiKey}` });
    const data = (json as { data?: Array<{ id?: string }> }).data ?? [];
    return uniqueSorted(data.map((entry) => entry.id ?? ''));
  }

  if (provider === 'anthropic') {
    const apiKey = resolveApiKey(provider, cfg);
    if (!apiKey) {throw new Error('Missing API key');}
    const json = await fetchJson('https://api.anthropic.com/v1/models', {
      'x-api-key': apiKey,
      'anthropic-version': '2023-06-01',
    });
    const data = (json as { data?: Array<{ id?: string }> }).data ?? [];
    return uniqueSorted(data.map((entry) => entry.id ?? ''));
  }

  if (provider === 'gemini') {
    const apiKey = resolveApiKey(provider, cfg);
    if (!apiKey) {throw new Error('Missing API key');}
    const json = await fetchJson(`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(apiKey)}`);
    const models = (json as { models?: Array<{ name?: string }> }).models ?? [];
    return uniqueSorted(models.map((entry) => (entry.name ?? '').replace(/^models\//, '')));
  }

  if (provider === 'openrouter') {
    const apiKey = resolveApiKey(provider, cfg);
    const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
    const json = await fetchJson('https://openrouter.ai/api/v1/models', headers);
    const data = (json as { data?: Array<{ id?: string }> }).data ?? [];
    return uniqueSorted(data.map((entry) => entry.id ?? ''));
  }

  if (provider === 'xai') {
    const apiKey = resolveApiKey(provider, cfg);
    if (!apiKey) {throw new Error('Missing API key');}
    const json = await fetchJson('https://api.x.ai/v1/models', { Authorization: `Bearer ${apiKey}` });
    const data = (json as { data?: Array<{ id?: string }> }).data ?? [];
    return uniqueSorted(data.map((entry) => entry.id ?? ''));
  }

  return [];
}

export function createModelCatalogFetcher(config: Config): ModelCatalogFetcher {
  const cache = new Map<ModelProvider, CacheEntry>();

  return async (opts) => {
    const provider = opts?.provider?.trim().toLowerCase();
    const providers = provider && MODEL_PROVIDERS.includes(provider as ModelProvider)
      ? [provider as ModelProvider]
      : [...MODEL_PROVIDERS];
    const providerConfigMap = getProviderConfigMap(config);
    const now = Date.now();

    const result = await Promise.all(providers.map(async (name): Promise<ProviderModelCatalog> => {
      if (!opts?.forceRefresh) {
        const cached = cache.get(name);
        if (cached && cached.expiresAt > now) {
          return cached.value;
        }
      }

      const configured = getConfiguredModelsForProvider(config, name);
      if (!SUPPORTED_PROVIDER_APIS.has(name)) {
        const value: ProviderModelCatalog = {
          provider: name,
          models: configured,
          source: configured.length > 0 ? 'config' : 'unavailable',
          fetchedAt: now,
        };
        cache.set(name, { expiresAt: now + CACHE_TTL_MS, value });
        return value;
      }

      try {
        const remoteModels = await fetchProviderModels(name, providerConfigMap[name]);
        const merged = uniqueSorted([...remoteModels, ...configured]);
        const value: ProviderModelCatalog = {
          provider: name,
          models: merged,
          source: remoteModels.length > 0 ? 'api' : (configured.length > 0 ? 'config' : 'unavailable'),
          fetchedAt: now,
        };
        cache.set(name, { expiresAt: now + CACHE_TTL_MS, value });
        return value;
      } catch (error) {
        const value: ProviderModelCatalog = {
          provider: name,
          models: configured,
          source: configured.length > 0 ? 'config' : 'unavailable',
          error: error instanceof Error ? error.message : String(error),
          fetchedAt: now,
        };
        cache.set(name, { expiresAt: now + CACHE_TTL_MS, value });
        return value;
      }
    }));

    return result;
  };
}
