import type { SecretStore } from '@mcpctl/shared';
import type { LlmFileConfig, LlmProviderFileEntry } from './http/config.js';
import { ProviderRegistry } from './providers/registry.js';
import { GeminiAcpProvider } from './providers/gemini-acp.js';
import { OllamaProvider } from './providers/ollama.js';
import { AnthropicProvider } from './providers/anthropic.js';
import { OpenAiProvider } from './providers/openai.js';
import { DeepSeekProvider } from './providers/deepseek.js';
import type { LlmProvider } from './providers/types.js';
import type { GeminiAcpConfig } from './providers/gemini-acp.js';
import type { OllamaConfig } from './providers/ollama.js';
import type { AnthropicConfig } from './providers/anthropic.js';
import type { OpenAiConfig } from './providers/openai.js';
import type { DeepSeekConfig } from './providers/deepseek.js';
import { ManagedVllmProvider } from './providers/vllm-managed.js';
import type { ManagedVllmConfig, ManagedVllmStatus } from './providers/vllm-managed.js';

/**
 * Thin wrapper that delegates all LlmProvider methods but overrides `name`.
 * Used when the user's chosen name (e.g. "vllm-local") differs from the
 * underlying provider's name (e.g. "openai").
 *
 * Also proxies `getStatus()` for managed providers so that status display
 * and health-check logic can detect managed lifecycle state.
 */
class NamedProvider implements LlmProvider {
  readonly name: string;
  private inner: LlmProvider;

  constructor(name: string, inner: LlmProvider) {
    this.name = name;
    this.inner = inner;

    // Proxy getStatus() from managed providers (e.g. ManagedVllmProvider)
    if ('getStatus' in inner && typeof (inner as ManagedVllmProvider).getStatus === 'function') {
      (this as unknown as { getStatus: () => ManagedVllmStatus }).getStatus =
        () => (inner as ManagedVllmProvider).getStatus();
    }
  }

  complete(...args: Parameters<LlmProvider['complete']>) {
    return this.inner.complete(...args);
  }
  listModels() {
    return this.inner.listModels();
  }
  isAvailable() {
    return this.inner.isAvailable();
  }
  dispose() {
    this.inner.dispose?.();
  }
  warmup() {
    this.inner.warmup?.();
  }
}

/**
 * Create a single LlmProvider from a provider entry config.
 * Returns null if required config is missing (logs warning).
 */
async function createSingleProvider(
  entry: LlmProviderFileEntry,
  secretStore: SecretStore,
): Promise<LlmProvider | null> {
  switch (entry.type) {
    case 'gemini-cli': {
      const cfg: GeminiAcpConfig = {};
      if (entry.binaryPath) cfg.binaryPath = entry.binaryPath;
      if (entry.model) cfg.defaultModel = entry.model;
      const provider = new GeminiAcpProvider(cfg);
      provider.warmup();
      return provider;
    }

    case 'ollama': {
      const cfg: OllamaConfig = {};
      if (entry.url) cfg.baseUrl = entry.url;
      if (entry.model) cfg.defaultModel = entry.model;
      return new OllamaProvider(cfg);
    }

    case 'anthropic': {
      const apiKey = await secretStore.get('anthropic-api-key');
      if (!apiKey) {
        process.stderr.write(`Warning: Anthropic API key not found for provider "${entry.name}". Run "mcpctl config setup" to configure.\n`);
        return null;
      }
      const cfg: AnthropicConfig = { apiKey };
      if (entry.model) cfg.defaultModel = entry.model;
      return new AnthropicProvider(cfg);
    }

    case 'openai': {
      const apiKey = await secretStore.get('openai-api-key');
      if (!apiKey) {
        process.stderr.write(`Warning: OpenAI API key not found for provider "${entry.name}". Run "mcpctl config setup" to configure.\n`);
        return null;
      }
      const cfg: OpenAiConfig = { apiKey };
      if (entry.url) cfg.baseUrl = entry.url;
      if (entry.model) cfg.defaultModel = entry.model;
      return new OpenAiProvider(cfg);
    }

    case 'deepseek': {
      const apiKey = await secretStore.get('deepseek-api-key');
      if (!apiKey) {
        process.stderr.write(`Warning: DeepSeek API key not found for provider "${entry.name}". Run "mcpctl config setup" to configure.\n`);
        return null;
      }
      const cfg: DeepSeekConfig = { apiKey };
      if (entry.url) cfg.baseUrl = entry.url;
      if (entry.model) cfg.defaultModel = entry.model;
      return new DeepSeekProvider(cfg);
    }

    case 'vllm': {
      if (!entry.url) {
        process.stderr.write(`Warning: vLLM URL not configured for provider "${entry.name}". Run "mcpctl config setup" to configure.\n`);
        return null;
      }
      return new OpenAiProvider({
        apiKey: 'unused',
        baseUrl: entry.url,
        defaultModel: entry.model ?? 'default',
      });
    }

    case 'vllm-managed': {
      if (!entry.venvPath) {
        process.stderr.write(`Warning: vLLM venv path not configured for "${entry.name}". Run "mcpctl config setup".\n`);
        return null;
      }
      const cfg: ManagedVllmConfig = {
        venvPath: entry.venvPath,
        model: entry.model ?? 'Qwen/Qwen2.5-7B-Instruct-AWQ',
      };
      if (entry.port !== undefined) cfg.port = entry.port;
      if (entry.gpuMemoryUtilization !== undefined) cfg.gpuMemoryUtilization = entry.gpuMemoryUtilization;
      if (entry.maxModelLen !== undefined) cfg.maxModelLen = entry.maxModelLen;
      if (entry.idleTimeoutMinutes !== undefined) cfg.idleTimeoutMinutes = entry.idleTimeoutMinutes;
      if (entry.extraArgs !== undefined) cfg.extraArgs = entry.extraArgs;
      return new ManagedVllmProvider(cfg);
    }

    default:
      return null;
  }
}

/**
 * Create a ProviderRegistry from multi-provider config entries + secret store.
 * Registers each provider, wraps with NamedProvider if needed, assigns tiers.
 */
export async function createProvidersFromConfig(
  entries: LlmProviderFileEntry[],
  secretStore: SecretStore,
): Promise<ProviderRegistry> {
  const registry = new ProviderRegistry();

  for (const entry of entries) {
    const rawProvider = await createSingleProvider(entry, secretStore);
    if (!rawProvider) continue;

    // Wrap with NamedProvider if user name differs from provider's built-in name
    const provider = rawProvider.name !== entry.name
      ? new NamedProvider(entry.name, rawProvider)
      : rawProvider;

    registry.register(provider);

    if (entry.tier) {
      registry.assignTier(provider.name, entry.tier);
    }
  }

  return registry;
}

/**
 * Create a ProviderRegistry from legacy single-provider config + secret store.
 * @deprecated Use createProvidersFromConfig() with loadLlmProviders() instead.
 */
export async function createProviderFromConfig(
  config: LlmFileConfig | undefined,
  secretStore: SecretStore,
): Promise<ProviderRegistry> {
  if (!config?.provider || config.provider === 'none') {
    return new ProviderRegistry();
  }

  const entry: LlmProviderFileEntry = {
    name: config.provider,
    type: config.provider,
  };
  if (config.model) entry.model = config.model;
  if (config.url) entry.url = config.url;
  if (config.binaryPath) entry.binaryPath = config.binaryPath;

  return createProvidersFromConfig([entry], secretStore);
}
