'use strict';

const NON_CODING_MODEL_RE = /(?:^|[-_:])(sora|image|audio|realtime|transcribe|transcription|speech|tts|whisper|embedding|embed|moderation|similarity|search|rerank|babbage|davinci|canary)(?:$|[-_:])/i;
const NON_CODING_NAME_RE = /\b(sora|image|audio|realtime|transcription|speech|tts|whisper|embedding|moderation|similarity|search|rerank)\b/i;
const CLI_AUTH_METHODS = new Set(['claude_cli', 'codex_cli']);
const KNOWN_CHAT_PROVIDER_TYPES = new Set(['anthropic', 'openai', 'google', 'deepseek', 'moonshot', 'ollama', 'lmstudio', 'mlx']);

function parseJsonMaybe(value, fallback = undefined) {
  if (!value) return fallback;
  if (typeof value !== 'string') return value;
  try { return JSON.parse(value); } catch { return fallback; }
}

function parseAnthropicEnvHeaders() {
  const raw = process.env.ANTHROPIC_CUSTOM_HEADERS_B64
    ? Buffer.from(process.env.ANTHROPIC_CUSTOM_HEADERS_B64, 'base64').toString()
    : (process.env.ANTHROPIC_CUSTOM_HEADERS || '');
  const headers = {};
  for (const line of raw.split('\n')) {
    const idx = line.indexOf(':');
    if (idx > 0) headers[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
  }
  return headers;
}

function parseCustomHeaders(value) {
  if (!value) return undefined;
  if (typeof value === 'object') return value;
  if (typeof value !== 'string') return undefined;
  const parsed = parseJsonMaybe(value, null);
  if (parsed && typeof parsed === 'object') return parsed;
  const headers = {};
  for (const line of value.split('\n')) {
    const idx = line.indexOf(':');
    if (idx > 0) headers[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
  }
  return Object.keys(headers).length ? headers : undefined;
}

function normalizeCapabilities(capabilities, providerType = '') {
  const out = new Set();
  if (Array.isArray(capabilities)) {
    for (const cap of capabilities) if (cap) out.add(String(cap).toLowerCase());
  } else if (capabilities && typeof capabilities === 'object') {
    for (const [key, value] of Object.entries(capabilities)) {
      if (value) out.add(String(key).toLowerCase());
    }
  } else if (typeof capabilities === 'string') {
    const parsed = parseJsonMaybe(capabilities, null);
    if (parsed && parsed !== capabilities) {
      for (const cap of normalizeCapabilities(parsed, providerType)) out.add(cap);
    } else {
      for (const cap of capabilities.split(/[,\s]+/)) if (cap) out.add(cap.toLowerCase());
    }
  }
  if (KNOWN_CHAT_PROVIDER_TYPES.has(String(providerType || '').toLowerCase())) out.add('chat');
  if (out.has('tool') || out.has('tool_calls') || out.has('tool-use') || out.has('tool_use')) out.add('tools');
  return Array.from(out).sort();
}

function isNonCodingModel(model = {}) {
  const id = String(model.id || model.model_id || model.modelId || '').toLowerCase();
  const name = String(model.name || model.display_name || model.displayName || '').toLowerCase();
  if (!id && !name) return true;
  if (/^(dall[-_]?e|gpt-image|gpt-audio|gpt-realtime|text-embedding|omni-moderation|computer-use)/i.test(id)) return true;
  if (NON_CODING_MODEL_RE.test(id)) return true;
  if (NON_CODING_NAME_RE.test(name)) return true;
  return false;
}

function isProviderModelFamily(model = {}, providerType = '') {
  const type = String(providerType || '').toLowerCase();
  const id = String(model.id || model.model_id || model.modelId || '').toLowerCase();
  if (!id) return false;
  if (type === 'anthropic') return id.startsWith('claude-');
  if (type === 'openai') return /^(gpt-|o[0-9]|codex|chatgpt-)/.test(id);
  if (type === 'google') return id.startsWith('gemini-');
  if (type === 'deepseek') return id.startsWith('deepseek-');
  if (type === 'moonshot') return id.startsWith('kimi-') || id.startsWith('moonshot-');
  return true;
}

function codingCapabilityReason(model = {}, providerType = '') {
  if (isNonCodingModel(model)) return { ok: false, reason: 'non-coding model family' };
  if (!isProviderModelFamily(model, providerType)) return { ok: false, reason: 'not a known chat model family' };
  const capabilities = normalizeCapabilities(model.capabilities, providerType);
  if (!capabilities.includes('chat')) return { ok: false, reason: 'missing chat capability' };
  if (!capabilities.includes('tools')) return { ok: false, reason: 'missing tool-call capability' };
  return { ok: true, reason: 'chat+tools' };
}

function isCodingCapableModel(model, providerType) {
  return codingCapabilityReason(model, providerType).ok;
}

function providerRuntimeForAvailability(provider = {}) {
  const type = String(provider.type || '').toLowerCase();
  const authMethod = String(provider.auth_method || 'api_key').toLowerCase();
  if (CLI_AUTH_METHODS.has(authMethod)) {
    return {
      skip: true,
      source: 'subscription-cli',
      status: 'unsupported_auth_method',
      error: 'CLI-backed auth does not expose provider tool-call model availability for Wall-E coding.',
    };
  }

  const config = {};
  if (provider.api_key_encrypted) config.apiKey = provider.api_key_encrypted;
  if (provider.base_url) config.baseUrl = provider.base_url;
  const customHeaders = parseCustomHeaders(provider.custom_headers);
  if (customHeaders) config.customHeaders = customHeaders;

  if (type === 'anthropic' && authMethod === 'oauth_proxy') {
    config.apiKey = config.apiKey || 'oauth-proxy-placeholder';
    config.baseUrl = config.baseUrl || `http://127.0.0.1:${process.env.OAUTH_PROXY_PORT || '3458'}`;
  } else if (type === 'anthropic' && authMethod === 'devbox') {
    config.apiKey = process.env.ANTHROPIC_API_KEY || config.apiKey;
    config.authToken = process.env.ANTHROPIC_AUTH_TOKEN || undefined;
    config.baseUrl = config.baseUrl || process.env.ANTHROPIC_BASE_URL;
    config.customHeaders = config.customHeaders || parseAnthropicEnvHeaders();
  } else if (type === 'google' && authMethod === 'gemini_cli') {
    config.authMode = 'oauth';
    config.refreshToken = process.env.GOOGLE_REFRESH_TOKEN || undefined;
  }

  return { type, config, source: 'live', status: 'pending' };
}

function normalizeCodingModel(model, provider, generatedAt) {
  const capabilities = normalizeCapabilities(model.capabilities, provider.type);
  const reason = codingCapabilityReason(model, provider.type);
  const modelId = model.id || model.model_id || model.modelId;
  return {
    id: `${provider.id}:${modelId}`,
    provider_id: provider.id,
    provider_type: provider.type,
    provider_name: provider.name || provider.type,
    model_id: modelId,
    display_name: model.name || model.display_name || model.displayName || modelId,
    capabilities,
    max_context_tokens: model.maxContextTokens || model.max_context_tokens || null,
    max_output_tokens: model.maxOutputTokens || model.max_output_tokens || null,
    size: model.size || null,
    enabled: 1,
    available: true,
    coding_capable: reason.ok,
    coding_reason: reason.reason,
    source: 'live',
    last_seen_at: generatedAt,
  };
}

async function listCodingModelAvailability({
  providers = [],
  getProviderWithKey,
  createClient,
  now = () => new Date(),
} = {}) {
  if (typeof createClient !== 'function') throw new Error('createClient is required');
  const generatedAt = now().toISOString();
  const models = [];
  const providerResults = [];

  for (const provider of providers) {
    if (!provider || provider.enabled === 0 || provider.enabled === false) continue;
    const full = typeof getProviderWithKey === 'function'
      ? (getProviderWithKey(provider.id) || provider)
      : provider;
    const base = {
      id: provider.id,
      name: provider.name || provider.type,
      type: provider.type,
      auth_method: full.auth_method || provider.auth_method || 'api_key',
      source: 'live',
      status: 'pending',
      models_count: 0,
      coding_count: 0,
      error: null,
    };

    const runtime = providerRuntimeForAvailability({ ...provider, ...full });
    if (runtime.skip) {
      providerResults.push({
        ...base,
        source: runtime.source,
        status: runtime.status,
        error: runtime.error,
      });
      continue;
    }

    try {
      const client = createClient(runtime.type, runtime.config);
      if (!client || typeof client.listModels !== 'function') {
        providerResults.push({ ...base, status: 'unsupported', error: 'Provider does not expose listModels()' });
        continue;
      }
      if (typeof client.isAvailable === 'function') {
        const available = await client.isAvailable();
        if (!available) {
          providerResults.push({ ...base, status: 'unavailable', error: 'Provider is not reachable' });
          continue;
        }
      }
      const listed = await client.listModels({ exact: true, liveOnly: true });
      const all = Array.isArray(listed) ? listed : [];
      const coding = all
        .filter((model) => isCodingCapableModel(model, provider.type))
        .map((model) => normalizeCodingModel(model, provider, generatedAt));
      models.push(...coding);
      providerResults.push({
        ...base,
        status: coding.length > 0 ? 'available' : (all.length > 0 ? 'no_coding_models' : 'empty'),
        models_count: all.length,
        coding_count: coding.length,
      });
    } catch (err) {
      providerResults.push({
        ...base,
        status: 'error',
        error: err && err.message ? err.message : String(err),
      });
    }
  }

  models.sort((a, b) => {
    const providerCmp = (a.provider_name || '').localeCompare(b.provider_name || '');
    if (providerCmp) return providerCmp;
    return (a.display_name || '').localeCompare(b.display_name || '', undefined, { numeric: true });
  });

  return {
    source: 'live',
    generated_at: generatedAt,
    models,
    providers: providerResults,
  };
}

module.exports = {
  normalizeCapabilities,
  isNonCodingModel,
  codingCapabilityReason,
  isCodingCapableModel,
  normalizeCodingModel,
  providerRuntimeForAvailability,
  listCodingModelAvailability,
};
