'use strict';

const { createClient } = require('./client');
const {
  isPortkeyProviderConfig,
  parseCustomHeaders,
} = require('./portkey');
const { findSupportedModel } = require('./supported-models');

const PORTKEY_SYNC_INTERVAL_MS = 60 * 60 * 1000;
const PORTKEY_SYNC_INITIAL_DELAY_MS = 15 * 1000;
const PORTKEY_GATEWAY_CREDENTIAL_TYPE = 'portkey_gateway';
const PORTKEY_KNOWN_PROVIDER_TYPES = new Set(['anthropic', 'deepseek', 'google', 'moonshot', 'openai']);
const PORTKEY_CATALOG_BASE_URL = 'https://configs.portkey.ai/pricing';
const PORTKEY_CATALOG_PROVIDERS_URL = 'https://raw.githubusercontent.com/Portkey-AI/models/main/README.md';
const PORTKEY_CATALOG_PROVIDERS = Object.freeze([
  ['ai21', 'AI21'],
  ['anthropic', 'Anthropic'],
  ['anyscale', 'Anyscale'],
  ['azure-ai', 'Azure AI'],
  ['azure-openai', 'Azure OpenAI'],
  ['bedrock', 'AWS Bedrock'],
  ['cerebras', 'Cerebras'],
  ['cohere', 'Cohere'],
  ['dashscope', 'Dashscope'],
  ['deepbricks', 'Deepbricks'],
  ['deepinfra', 'DeepInfra'],
  ['deepseek', 'DeepSeek'],
  ['fireworks-ai', 'Fireworks AI'],
  ['github', 'GitHub'],
  ['google', 'Google'],
  ['groq', 'Groq'],
  ['inference-net', 'Inference.net'],
  ['jina', 'Jina'],
  ['lambda', 'Lambda'],
  ['lemonfox-ai', 'Lemonfox AI'],
  ['mistral-ai', 'Mistral AI'],
  ['monsterapi', 'MonsterAPI'],
  ['nebius', 'Nebius'],
  ['nomic', 'Nomic'],
  ['novita-ai', 'Novita AI'],
  ['openai', 'OpenAI'],
  ['openrouter', 'OpenRouter'],
  ['oracle', 'Oracle'],
  ['palm', 'PaLM'],
  ['perplexity-ai', 'Perplexity AI'],
  ['predibase', 'Predibase'],
  ['reka-ai', 'Reka AI'],
  ['sagemaker', 'Sagemaker'],
  ['segmind', 'Segmind'],
  ['stability-ai', 'Stability AI'],
  ['together-ai', 'Together AI'],
  ['vertex-ai', 'Vertex AI'],
  ['workers-ai', 'Workers AI'],
  ['x-ai', 'X.AI'],
  ['zhipu', 'Zhipu'],
].map(([slug, name]) => Object.freeze({ slug, name })));
const PORTKEY_CATALOG_PROVIDER_SLUGS_BY_NAME = new Map(
  PORTKEY_CATALOG_PROVIDERS.map((provider) => [normalizePortkeyProviderName(provider.name), provider.slug])
);

function isPortkeyGatewayCredentialRow(row = {}) {
  const type = String(row.type || row.provider_type || '').toLowerCase();
  const id = String(row.id || row.provider_id || '').toLowerCase();
  return type === PORTKEY_GATEWAY_CREDENTIAL_TYPE || id.startsWith('portkey-gateway-');
}

function isPortkeyProviderRow(row = {}) {
  if (isPortkeyGatewayCredentialRow(row)) return false;
  return isPortkeyProviderConfig({
    baseUrl: row.base_url || row.baseUrl,
    customHeaders: row.custom_headers || row.customHeaders,
  });
}

function modelLookupId(modelId) {
  const id = String(modelId || '').trim();
  const slash = id.lastIndexOf('/');
  return slash >= 0 ? id.slice(slash + 1) : id;
}

function prettyGatewayModelName(model = {}) {
  const id = String(model.id || model.model_id || model.modelId || '').trim();
  if (model.name && model.name !== id) return model.name;
  const base = modelLookupId(id);
  return base || id;
}

function prettyProviderName(providerType) {
  const slug = String(providerType || '').trim().toLowerCase();
  const row = PORTKEY_CATALOG_PROVIDERS.find((provider) => provider.slug === slug);
  if (row) return row.name;
  return slug
    .split(/[-_]+/)
    .filter(Boolean)
    .map((part) => part.length <= 3 ? part.toUpperCase() : part.charAt(0).toUpperCase() + part.slice(1))
    .join(' ') || 'Provider';
}

function normalizePortkeyProviderName(value) {
  return String(value || '')
    .trim()
    .toLowerCase()
    .replace(/&amp;/g, '&')
    .replace(/[^a-z0-9]+/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();
}

function slugFromPortkeyProviderName(name) {
  const normalized = normalizePortkeyProviderName(name);
  if (PORTKEY_CATALOG_PROVIDER_SLUGS_BY_NAME.has(normalized)) {
    return PORTKEY_CATALOG_PROVIDER_SLUGS_BY_NAME.get(normalized);
  }
  return normalized.replace(/\s+/g, '-').slice(0, 40);
}

function inferGatewayCapabilities(providerType, model = {}) {
  if (Array.isArray(model.capabilities) || (model.capabilities && typeof model.capabilities === 'object')) {
    return model.capabilities;
  }
  const id = String(model.id || model.model_id || model.modelId || '').trim();
  const supported = findSupportedModel(providerType, id) || findSupportedModel(providerType, modelLookupId(id));
  return supported?.capabilities || ['chat'];
}

function gatewayModelVerificationStatus(providerType, model = {}) {
  const id = String(model.id || model.model_id || model.modelId || '').trim();
  return findSupportedModel(providerType, id) || findSupportedModel(providerType, modelLookupId(id))
    ? 'verified'
    : 'unverified';
}

function providerConfigFromRow(row = {}) {
  const config = {};
  if (row.api_key_encrypted) config.apiKey = row.api_key_encrypted;
  if (row.base_url) config.baseUrl = row.base_url;
  const customHeaders = parseCustomHeaders(row.custom_headers || row.customHeaders);
  if (Object.keys(customHeaders).length > 0) config.customHeaders = customHeaders;
  return config;
}

function portkeyModelsUrl(baseUrl, providerType) {
  let root = String(baseUrl || 'https://api.portkey.ai/v1').trim().replace(/\/+$/, '');
  if (/^https:\/\/api\.portkey\.ai$/i.test(root)) root += '/v1';
  const url = new URL(root + '/models');
  if (providerType) url.searchParams.set('ai_service', providerType);
  return url;
}

function portkeyCatalogUrl(providerSlug) {
  const slug = String(providerSlug || '').trim().toLowerCase();
  if (!slug || !/^[a-z0-9-]+$/.test(slug)) return null;
  return new URL(`${PORTKEY_CATALOG_BASE_URL}/${slug}.json`);
}

function portkeyPriceToCostPer1m(value) {
  const priceCentsPerToken = Number(value);
  if (!Number.isFinite(priceCentsPerToken)) return null;
  return priceCentsPerToken * 10000;
}

function extractPortkeyPricing(modelConfig = {}) {
  const config = modelConfig.pricing_config || modelConfig;
  const payg = config.pay_as_you_go || {};
  return {
    costPer1mInput: portkeyPriceToCostPer1m(payg.request_token && payg.request_token.price),
    costPer1mOutput: portkeyPriceToCostPer1m(payg.response_token && payg.response_token.price),
  };
}

function normalizePortkeyCatalogModels(provider, payload = {}) {
  const providerSlug = typeof provider === 'string' ? provider : provider && provider.slug;
  const providerName = (provider && provider.name) || prettyProviderName(providerSlug);
  const out = [];
  for (const [modelId, modelConfig] of Object.entries(payload || {})) {
    if (!modelId || modelId === 'default' || !modelConfig || typeof modelConfig !== 'object') continue;
    const pricing = extractPortkeyPricing(modelConfig);
    out.push({
      id: `@${providerSlug}/${modelId}`,
      name: modelId,
      provider: providerSlug,
      ai_service: providerSlug,
      provider_name: providerName,
      canonical_slug: modelId,
      source: 'portkey_catalog',
      costPer1mInput: pricing.costPer1mInput,
      costPer1mOutput: pricing.costPer1mOutput,
    });
  }
  return out;
}

async function listPortkeyCatalogModelsForProvider(provider, fetchFn = fetch) {
  const info = typeof provider === 'string'
    ? { slug: provider, name: prettyProviderName(provider) }
    : provider;
  const url = portkeyCatalogUrl(info && info.slug);
  if (!url) return [];
  const options = {};
  if (typeof AbortSignal !== 'undefined' && AbortSignal.timeout) {
    options.signal = AbortSignal.timeout(8000);
  }
  const response = await fetchFn(url, options);
  if (!response.ok) return [];
  const data = await response.json();
  return normalizePortkeyCatalogModels(info, data);
}

function parsePortkeyCatalogProviders(markdown = '') {
  const providersBlock = String(markdown || '').match(/## Providers[\s\S]*?<summary>[\s\S]*?<\/summary>\s*([\s\S]*?)<\/details>/i);
  if (!providersBlock) return [];
  const seen = new Set();
  return providersBlock[1]
    .replace(/<[^>]+>/g, ' ')
    .split(',')
    .map((name) => name.trim())
    .filter(Boolean)
    .map((name) => ({ slug: slugFromPortkeyProviderName(name), name }))
    .filter((provider) => {
      if (!provider.slug || seen.has(provider.slug)) return false;
      seen.add(provider.slug);
      return true;
    });
}

async function listPortkeyCatalogProviders({ fetchFn = fetch } = {}) {
  const options = {};
  if (typeof AbortSignal !== 'undefined' && AbortSignal.timeout) {
    options.signal = AbortSignal.timeout(8000);
  }
  try {
    const response = await fetchFn(PORTKEY_CATALOG_PROVIDERS_URL, options);
    if (!response.ok) return PORTKEY_CATALOG_PROVIDERS;
    const text = await response.text();
    const providers = parsePortkeyCatalogProviders(text);
    return providers.length ? providers : PORTKEY_CATALOG_PROVIDERS;
  } catch {
    return PORTKEY_CATALOG_PROVIDERS;
  }
}

async function listPortkeyCatalogModels({
  providers = PORTKEY_CATALOG_PROVIDERS,
  fetchFn = fetch,
  listProviderFn = listPortkeyCatalogModelsForProvider,
} = {}) {
  const out = [];
  for (const provider of providers || []) {
    try {
      const models = await listProviderFn(provider, fetchFn);
      if (Array.isArray(models) && models.length) out.push(...models);
    } catch {
      // Provider catalog files are best-effort; unsupported or unavailable
      // providers should not break the gateway sync.
    }
  }
  return out;
}

function summarizePortkeyModelListError(status, text) {
  let message = String(text || '').trim();
  try {
    const parsed = JSON.parse(message);
    message = parsed && parsed.message ? String(parsed.message) : message;
  } catch {}
  if (!message) message = 'No response body';
  const hint = status === 401 || status === 403
    ? ' Check the Portkey API key and any Config ID or Virtual Key on the gateway.'
    : '';
  return `Portkey models request failed (${status}): ${message.slice(0, 160)}${hint}`;
}

async function listPortkeyModelsForGateway(gateway, fetchFn = fetch) {
  const config = providerConfigFromRow(gateway);
  const headers = { ...(config.customHeaders || {}) };
  if (config.apiKey && !headers['x-portkey-api-key']) headers['x-portkey-api-key'] = config.apiKey;
  const url = portkeyModelsUrl(config.baseUrl);
  const response = await fetchFn(url, { headers });
  if (!response.ok) {
    const text = await response.text().catch(() => '');
    throw new Error(summarizePortkeyModelListError(response.status, text));
  }
  const data = await response.json();
  return Array.isArray(data?.data) ? data.data : [];
}

const listPortkeyModelsForProvider = listPortkeyModelsForGateway;

function normalizePortkeyProviderType(value) {
  const raw = String(value || '').trim().toLowerCase();
  if (!raw) return '';
  if (raw.includes('anthropic') || raw.includes('claude')) return 'anthropic';
  if (raw.includes('deepseek')) return 'deepseek';
  if (raw.includes('gemini') || raw.includes('google') || raw.includes('vertex')) return 'google';
  if (raw.includes('moonshot') || raw.includes('kimi')) return 'moonshot';
  if (raw.includes('openai') || raw.includes('azure-openai')) return 'openai';
  return raw
    .replace(/^@/, '')
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '')
    .slice(0, 40);
}

function normalizePortkeyCatalogProviderType(value) {
  return String(value || '')
    .trim()
    .toLowerCase()
    .replace(/^@/, '')
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '')
    .slice(0, 40);
}

function inferPortkeyProviderTypeFromModelName(value) {
  const id = modelLookupId(value).toLowerCase();
  if (!id) return '';
  if (id.startsWith('claude-')) return 'anthropic';
  if (id.startsWith('deepseek-')) return 'deepseek';
  if (id.startsWith('gemini-')) return 'google';
  if (id.startsWith('kimi-') || id.startsWith('moonshot-')) return 'moonshot';
  if (id.startsWith('gpt-') || id.startsWith('o1') || id.startsWith('o3') || id.startsWith('o4') || id.startsWith('o5') || id.startsWith('text-embedding-')) return 'openai';
  return '';
}

function inferPortkeyProviderType(model = {}) {
  const id = String(model.id || model.model_id || model.modelId || '').trim();
  const prefix = id.match(/^@([^/]+)\//);
  if (model.source === 'portkey_catalog') {
    const catalogType = normalizePortkeyCatalogProviderType(model.provider || model.ai_service || model.aiService || (prefix && prefix[1]));
    if (catalogType) return catalogType;
  }
  const explicitCandidates = [
    model.ai_service,
    model.aiService,
    model.provider_type,
    model.providerType,
    model.provider,
    model.owned_by,
  ];
  for (const candidate of explicitCandidates) {
    const normalized = normalizePortkeyProviderType(candidate);
    if (normalized) return normalized;
  }
  const prefixType = normalizePortkeyProviderType(prefix && prefix[1]);
  if (PORTKEY_KNOWN_PROVIDER_TYPES.has(prefixType)) return prefixType;
  const inferredFromName = inferPortkeyProviderTypeFromModelName(model.canonical_slug || model.canonicalSlug || model.slug || id);
  if (inferredFromName) return inferredFromName;
  if (prefixType) return prefixType;
  return '';
}

function portkeyGatewaySuffix(row = {}) {
  const id = String(row.id || '').trim();
  const gatewayPrefix = 'portkey-gateway-';
  if (id.startsWith(gatewayPrefix)) {
    const suffix = id.slice(gatewayPrefix.length);
    return suffix ? `portkey-${suffix}` : 'portkey';
  }
  const idx = id.indexOf('-portkey');
  if (idx >= 0) return id.slice(idx + 1);
  return 'portkey';
}

function portkeyRouteProviderId(providerType, gateway) {
  return `${providerType}-${portkeyGatewaySuffix(gateway)}`;
}

function portkeyRouteProviderName(providerType, gateway) {
  const label = String(gateway.name || '').replace(/^Portkey Gateway/i, '').trim();
  const knownNames = {
    anthropic: 'Anthropic',
    deepseek: 'DeepSeek',
    google: 'Google Gemini',
    moonshot: 'Moonshot / Kimi',
    openai: 'OpenAI',
  };
  const title = knownNames[providerType] || prettyProviderName(providerType);
  return `${title} via Portkey${label ? ` ${label}` : ''}`;
}

function portkeyCatalogRouteProviderId(providerType, gateway) {
  return `${providerType}-portkey-catalog-${portkeyGatewaySuffix(gateway).replace(/^portkey-/, '')}`;
}

function portkeyCatalogRouteProviderName(providerType, gateway) {
  const label = String(gateway.name || '').replace(/^Portkey Gateway/i, '').trim();
  return `${prettyProviderName(providerType)} via Portkey catalog${label ? ` ${label}` : ''}`;
}

function routeHeadersForPortkeyModel(routeConfig, model = {}) {
  const headers = { ...(routeConfig.customHeaders || {}) };
  if (model.source === 'portkey_catalog') {
    delete headers['x-portkey-provider'];
  }
  return headers;
}

function portkeyRuntimeBaseUrl(baseUrl, providerType) {
  const normalized = String(baseUrl || 'https://api.portkey.ai/v1').trim().replace(/\/+$/, '');
  if (providerType === 'anthropic' && /^https:\/\/api\.portkey\.ai\/v1$/i.test(normalized)) {
    return 'https://api.portkey.ai';
  }
  if (providerType !== 'anthropic' && /^https:\/\/api\.portkey\.ai$/i.test(normalized)) {
    return 'https://api.portkey.ai/v1';
  }
  return normalized || 'https://api.portkey.ai/v1';
}

function portkeyCredentialRows(rows, requestedProviderId) {
  const allPortkey = rows.filter((row) => isPortkeyProviderConfig({
    baseUrl: row.base_url || row.baseUrl,
    customHeaders: row.custom_headers || row.customHeaders,
  }));
  const requested = requestedProviderId
    ? allPortkey.find((row) => row.id === requestedProviderId)
    : null;
  const explicit = allPortkey.filter(isPortkeyGatewayCredentialRow);
  const credentials = explicit.length ? explicit : allPortkey.slice(0, 1);
  return {
    credentials: requested && isPortkeyGatewayCredentialRow(requested) ? [requested] : credentials,
    targetProviderType: requested && isPortkeyProviderRow(requested) ? requested.type : '',
  };
}

async function syncPortkeyGatewayModels({
  brainApi,
  createClientFn = null,
  listPortkeyModelsFn = listPortkeyModelsForGateway,
  listPortkeyCatalogProvidersFn = listPortkeyCatalogProviders,
  listPortkeyCatalogModelsFn = listPortkeyCatalogModels,
  catalogProviders = null,
  providerId,
  now = new Date().toISOString(),
} = {}) {
  const brain = brainApi || require('../brain');
  const rows = (brain.listModelProviders?.() || [])
    .filter((provider) => provider.enabled !== 0 && provider.enabled !== false)
    .map((provider) => brain.getModelProviderWithKey?.(provider.id) || provider)
    .filter((provider) => !providerId || provider.id === providerId || isPortkeyGatewayCredentialRow(provider) || isPortkeyProviderRow(provider));
  const { credentials, targetProviderType } = portkeyCredentialRows(rows, providerId);

  const result = {
    gateway: 'portkey',
    synced_at: now,
    routes: [],
    imported: 0,
    updated: 0,
    errors: [],
    warnings: [],
  };

  let resolvedCatalogProviders = Array.isArray(catalogProviders) ? catalogProviders : null;
  for (const gateway of credentials) {
    const gatewayRoute = {
      gateway_id: gateway.id,
      models_seen: 0,
      imported: 0,
      updated: 0,
    };
    const isGatewayCredential = isPortkeyGatewayCredentialRow(gateway);
    let listed = [];
    let catalogListed = [];
    let liveError = null;
    let catalogError = null;

    try {
      listed = createClientFn
        ? await (() => {
            const client = createClientFn('portkey', providerConfigFromRow(gateway));
            if (!client || typeof client.listModels !== 'function') {
              throw new Error('Provider does not expose listModels()');
            }
            return client.listModels({
              exact: true,
              liveOnly: true,
              includeUnknown: true,
              allowUnknown: true,
              source: 'portkey',
            });
          })()
        : await listPortkeyModelsFn(gateway);
    } catch (err) {
      liveError = err && err.message ? err.message : String(err);
    }

    if (isGatewayCredential) {
      try {
        if (!resolvedCatalogProviders) {
          resolvedCatalogProviders = listPortkeyCatalogProvidersFn
            ? listPortkeyCatalogProvidersFn()
            : PORTKEY_CATALOG_PROVIDERS;
        }
        catalogListed = await Promise.resolve(resolvedCatalogProviders)
          .then((providers) => listPortkeyCatalogModelsFn({ providers }));
      } catch (err) {
        catalogError = err && err.message ? err.message : String(err);
      }
    }

    const combinedModels = (Array.isArray(listed) ? listed : []).concat(Array.isArray(catalogListed) ? catalogListed : []);
    if (!combinedModels.length && (liveError || catalogError)) {
      gatewayRoute.error = [liveError, catalogError].filter(Boolean).join('; ');
      result.errors.push({ gateway_id: gateway.id, error: gatewayRoute.error });
      result.routes.push(gatewayRoute);
      continue;
    }

    if (liveError) {
      gatewayRoute.warning = liveError;
      result.warnings.push({ gateway_id: gateway.id, scope: 'live_model_list', warning: liveError });
    }
    if (catalogError) {
      gatewayRoute.warning = gatewayRoute.warning ? `${gatewayRoute.warning}; ${catalogError}` : catalogError;
      result.warnings.push({ gateway_id: gateway.id, scope: 'catalog_model_list', warning: catalogError });
    }

    try {
      const seenByRoute = new Map();
      for (const model of combinedModels) {
        const modelId = String(model && (model.id || model.model_id || model.modelId) || '').trim();
        const providerType = inferPortkeyProviderType(model);
        if (!modelId || !providerType || (targetProviderType && providerType !== targetProviderType)) continue;
        const isCatalogModel = model.source === 'portkey_catalog';
        const providerRowId = isCatalogModel
          ? portkeyCatalogRouteProviderId(providerType, gateway)
          : portkeyRouteProviderId(providerType, gateway);
        const seen = seenByRoute.get(providerRowId) || new Set();
        if (seen.has(modelId)) continue;
        seen.add(modelId);
        seenByRoute.set(providerRowId, seen);
        const routeConfig = providerConfigFromRow(gateway);
        const routeHeaders = routeHeadersForPortkeyModel(routeConfig, model);
        brain.upsertModelProvider?.({
          id: providerRowId,
          name: isCatalogModel ? portkeyCatalogRouteProviderName(providerType, gateway) : portkeyRouteProviderName(providerType, gateway),
          type: providerType,
          baseUrl: portkeyRuntimeBaseUrl(routeConfig.baseUrl, providerType),
          apiKeyEncrypted: routeConfig.apiKey,
          customHeaders: JSON.stringify(routeHeaders || {}),
          enabled: 1,
        });
        const registryId = `${providerRowId}:${modelId}`;
        const existing = brain.getModelRegistryEntry?.(registryId);
        brain.upsertModelRegistryEntry({
          id: registryId,
          providerId: providerRowId,
          modelId,
          displayName: prettyGatewayModelName({ ...model, id: modelId }),
          capabilities: inferGatewayCapabilities(providerType, { ...model, id: modelId }),
          costPer1mInput: model.costPer1mInput || model.cost_per_1m_input || null,
          costPer1mOutput: model.costPer1mOutput || model.cost_per_1m_output || null,
          maxContextTokens: model.maxContextTokens || model.max_context_tokens || null,
          maxOutputTokens: model.maxOutputTokens || model.max_output_tokens || null,
          speedTier: model.speedTier || model.speed_tier || 3,
          enabled: true,
          source: isCatalogModel ? 'portkey_catalog' : 'portkey',
          gatewayType: 'portkey',
          verificationStatus: isCatalogModel ? 'catalog' : gatewayModelVerificationStatus(providerType, { ...model, id: modelId }),
          lastSeenAt: now,
        });
        if (existing) gatewayRoute.updated += 1;
        else gatewayRoute.imported += 1;
      }
      gatewayRoute.models_seen = Array.from(seenByRoute.values()).reduce((sum, seen) => sum + seen.size, 0);
      result.imported += gatewayRoute.imported;
      result.updated += gatewayRoute.updated;
      result.routes.push(gatewayRoute);
    } catch (err) {
      gatewayRoute.error = err && err.message ? err.message : String(err);
      result.errors.push({ gateway_id: gateway.id, error: gatewayRoute.error });
      result.routes.push(gatewayRoute);
    }
  }

  if (typeof brain.setKv === 'function') {
    brain.setKv('model_gateway_sync:portkey:last_run_at', now);
    if (result.errors.length === 0) {
      brain.setKv('model_gateway_sync:portkey:last_success_at', now);
      brain.setKv('model_gateway_sync:portkey:last_error', '');
    } else {
      brain.setKv('model_gateway_sync:portkey:last_error', result.errors.map((item) => `${item.gateway_id}: ${item.error}`).join('; '));
    }
  }

  return result;
}

function startPortkeyModelSyncLoop(options = {}) {
  const intervalMs = Number.isFinite(options.intervalMs) ? options.intervalMs : PORTKEY_SYNC_INTERVAL_MS;
  const initialDelayMs = Number.isFinite(options.initialDelayMs) ? options.initialDelayMs : PORTKEY_SYNC_INITIAL_DELAY_MS;
  const log = options.log || console;
  let stopped = false;
  let timer = null;
  let running = false;

  const schedule = (delay) => {
    if (stopped) return;
    timer = setTimeout(run, delay);
    if (timer && typeof timer.unref === 'function') timer.unref();
  };

  const run = async () => {
    if (stopped) return;
    if (running) {
      schedule(intervalMs);
      return;
    }
    running = true;
    try {
      const result = await syncPortkeyGatewayModels(options);
      if ((result.imported || result.updated) && log && typeof log.log === 'function') {
        log.log(`[portkey-sync] synced ${result.imported} new and ${result.updated} existing model(s)`);
      }
    } catch (err) {
      if (log && typeof log.warn === 'function') log.warn('[portkey-sync] failed:', err.message);
    } finally {
      running = false;
      schedule(intervalMs);
    }
  };

  schedule(initialDelayMs);
  return () => {
    stopped = true;
    if (timer) clearTimeout(timer);
  };
}

module.exports = {
  PORTKEY_SYNC_INTERVAL_MS,
  PORTKEY_GATEWAY_CREDENTIAL_TYPE,
  PORTKEY_CATALOG_PROVIDERS,
  PORTKEY_CATALOG_PROVIDERS_URL,
  isPortkeyGatewayCredentialRow,
  isPortkeyProviderRow,
  modelLookupId,
  inferGatewayCapabilities,
  gatewayModelVerificationStatus,
  inferPortkeyProviderType,
  providerConfigFromRow,
  portkeyModelsUrl,
  portkeyCatalogUrl,
  normalizePortkeyCatalogModels,
  parsePortkeyCatalogProviders,
  listPortkeyCatalogProviders,
  listPortkeyCatalogModelsForProvider,
  listPortkeyCatalogModels,
  summarizePortkeyModelListError,
  listPortkeyModelsForGateway,
  listPortkeyModelsForProvider,
  syncPortkeyGatewayModels,
  startPortkeyModelSyncLoop,
};
