import type { ConfigModelProvider } from '../config/types.js';
import BaseModelProvider, {
  createProviderInstance,
} from './base/provider.js';
import { getConfiguredModelProviders } from '../config/serverRegistry.js';
import { providers } from './providers/index.js';
import type { MinimalProvider, ModelList } from './types.js';
import { providersConfig } from '../config/ProvidersConfig.js';

class ModelRegistry {
  activeProviders: (ConfigModelProvider & {
    provider: BaseModelProvider<unknown>;
  })[] = [];

  constructor() {
    this.initializeActiveProviders();
  }

  private initializeActiveProviders(): void {
    const configuredProviders = getConfiguredModelProviders();

    configuredProviders.forEach((p) => {
      try {
        const providerCtor = providers[p.type];
        if (!providerCtor) throw new Error(`Invalid provider type: ${p.type}`);

        this.activeProviders.push({
          ...p,
          provider: createProviderInstance(
            providerCtor,
            p.id,
            p.name,
            p.config,
          ),
        });
      } catch (err) {
        console.error(
          `Failed to initialize provider. Type: ${p.type}, ID: ${p.id}, Config: ${JSON.stringify(p.config)}, Error: ${err}`,
        );
      }
    });
  }

  async getActiveProviders(): Promise<MinimalProvider[]> {
    const result: MinimalProvider[] = [];

    await Promise.all(
      this.activeProviders.map(async (p) => {
        let m: ModelList = { chat: [], embedding: [] };

        try {
          m = await p.provider.getModelList();
        } catch (err: unknown) {
          const msg = err instanceof Error ? err.message : String(err);
          console.error(
            `Failed to get model list. Type: ${p.type}, ID: ${p.id}, Error: ${msg}`,
          );
          m = {
            chat: [{ key: 'error', name: msg }],
            embedding: [],
          };
        }

        result.push({
          id: p.id,
          name: p.name,
          chatModels: m.chat,
          embeddingModels: m.embedding,
        });
      }),
    );

    return result;
  }

  async loadChatModel(providerId: string, modelName: string) {
    const provider = this.activeProviders.find((p) => p.id === providerId);
    if (!provider) throw new Error('Invalid provider id');
    return provider.provider.loadChatModel(modelName);
  }

  async loadEmbeddingModel(providerId: string, modelName: string) {
    const provider = this.activeProviders.find((p) => p.id === providerId);
    if (!provider) throw new Error('Invalid provider id');
    return provider.provider.loadEmbeddingModel(modelName);
  }

  async addProvider(
    type: string,
    name: string,
    config: Record<string, unknown>,
  ): Promise<ConfigModelProvider> {
    const providerCtor = providers[type];
    if (!providerCtor) throw new Error('Invalid provider type');

    const newProvider = providersConfig.addModelProvider(type, name, config);
    const instance = createProviderInstance(
      providerCtor,
      newProvider.id,
      newProvider.name,
      newProvider.config,
    );

    let m: ModelList = { chat: [], embedding: [] };
    try {
      m = await instance.getModelList();
    } catch (err: unknown) {
      const msg = err instanceof Error ? err.message : String(err);
      console.error(
        `Failed to get model list for newly added provider. Type: ${type}, ID: ${newProvider.id}, Error: ${msg}`,
      );
      m = {
        chat: [{ key: 'error', name: msg }],
        embedding: [],
      };
    }

    this.activeProviders.push({
      ...newProvider,
      provider: instance,
    });

    return {
      ...newProvider,
      chatModels: m.chat,
      embeddingModels: m.embedding,
    };
  }

  async removeProvider(providerId: string): Promise<void> {
    providersConfig.removeModelProvider(providerId);
    this.activeProviders = this.activeProviders.filter(
      (p) => p.id !== providerId,
    );
  }

  async updateProvider(
    providerId: string,
    name: string,
    config: Record<string, unknown>,
  ): Promise<ConfigModelProvider> {
    const updated = providersConfig.updateModelProvider(
      providerId,
      name,
      config,
    );
    const providerCtor = providers[updated.type];
    if (!providerCtor) throw new Error('Invalid provider type');

    const instance = createProviderInstance(
      providerCtor,
      providerId,
      name,
      config,
    );

    let m: ModelList = { chat: [], embedding: [] };
    try {
      m = await instance.getModelList();
    } catch (err: unknown) {
      const msg = err instanceof Error ? err.message : String(err);
      console.error(
        `Failed to get model list for updated provider. Type: ${updated.type}, ID: ${updated.id}, Error: ${msg}`,
      );
      m = {
        chat: [{ key: 'error', name: msg }],
        embedding: [],
      };
    }

    const idx = this.activeProviders.findIndex((p) => p.id === providerId);
    if (idx >= 0) {
      this.activeProviders[idx] = {
        ...updated,
        provider: instance,
        chatModels: m.chat,
        embeddingModels: m.embedding,
      };
    } else {
      this.activeProviders.push({
        ...updated,
        provider: instance,
        chatModels: m.chat,
        embeddingModels: m.embedding,
      });
    }

    return {
      ...updated,
      chatModels: m.chat,
      embeddingModels: m.embedding,
    };
  }

  async addProviderModel(
    providerId: string,
    type: 'embedding' | 'chat',
    model: { key: string; name: string },
  ): Promise<void> {
    providersConfig.addProviderModel(providerId, type, model);
  }

  async removeProviderModel(
    providerId: string,
    type: 'embedding' | 'chat',
    modelKey: string,
  ): Promise<void> {
    providersConfig.removeProviderModel(providerId, type, modelKey);
  }
}

export default ModelRegistry;
