import path from 'node:path';
import fs from 'node:fs';
import crypto from 'node:crypto';
import type { ConfigModelProvider } from './types.js';
import { hashObj } from '../serverUtils.js';

const defaultProvidersConfig = { modelProviders: [] };

class ProvidersConfig {
  private configPath: string;
  private currentConfig: { modelProviders: ConfigModelProvider[] } =
    defaultProvidersConfig;

  constructor() {
    const dataDir = process.env.DATA_DIR
      ? path.resolve(process.cwd(), process.env.DATA_DIR)
      : process.cwd();
    this.configPath = path.join(dataDir, 'data', 'llm-providers.json');
    this.initialize();
  }

  private ensureDir(): void {
    const dir = path.dirname(this.configPath);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }
  }

  private load(): void {
    this.ensureDir();
    if (fs.existsSync(this.configPath)) {
      try {
        const raw = fs.readFileSync(this.configPath, 'utf-8');
        this.currentConfig = JSON.parse(raw) as {
          modelProviders: ConfigModelProvider[];
        };
        if (!Array.isArray(this.currentConfig.modelProviders)) {
          this.currentConfig.modelProviders = [];
        }
      } catch (err) {
        if (err instanceof SyntaxError) {
          console.error(
            `Error parsing llm-providers.json at ${this.configPath}:`,
            err,
          );
          this.currentConfig = { ...defaultProvidersConfig };
          this.save();
        } else {
          throw err;
        }
      }
    } else {
      this.currentConfig = { ...defaultProvidersConfig };
      this.save();
    }
  }

  private initialize(): void {
    this.load();
    const envProvider = this.buildEnvProvider();
    if (envProvider) {
      this.currentConfig.modelProviders = [envProvider];
      this.save();
    }
  }

  private save(): void {
    this.ensureDir();
    fs.writeFileSync(
      this.configPath,
      JSON.stringify(this.currentConfig, null, 2),
    );
  }

  /** Env-only mode: LLM from LLM_PROVIDER=ollama|timeweb, no CRUD for providers */
  public isEnvOnlyMode(): boolean {
    return ['ollama', 'timeweb'].includes(
      (process.env.LLM_PROVIDER ?? '').toLowerCase(),
    );
  }

  private buildEnvProvider(): ConfigModelProvider | null {
    const providerType = (process.env.LLM_PROVIDER ?? '').toLowerCase();

    if (providerType === 'ollama') {
      const defaultUrl =
        process.env.DOCKER
          ? 'http://host.docker.internal:11434'
          : 'http://localhost:11434';
      const baseURL = process.env.OLLAMA_BASE_URL ?? defaultUrl;
      const embeddingBaseURL =
        process.env.OLLAMA_EMBEDDING_BASE_URL ?? baseURL;
      const chatModel = process.env.LLM_CHAT_MODEL ?? 'llama3.2';
      const embeddingModel =
        process.env.LLM_EMBEDDING_MODEL ?? 'nomic-embed-text';

      const config: Record<string, string> = {
        baseURL,
        ...(embeddingBaseURL !== baseURL && { embeddingBaseURL }),
      };
      return {
        id: 'env-ollama',
        name: 'Ollama',
        type: 'ollama',
        chatModels: [{ key: chatModel, name: chatModel }],
        embeddingModels: [{ key: embeddingModel, name: embeddingModel }],
        config,
        hash: hashObj(config),
      };
    }

    if (providerType === 'timeweb') {
      const baseURL =
        process.env.TIMEWEB_API_BASE_URL ?? 'https://api.timeweb.cloud';
      const agentAccessId = process.env.TIMEWEB_AGENT_ACCESS_ID ?? '';
      const apiKey = process.env.TIMEWEB_API_KEY ?? '';
      const model = process.env.LLM_CHAT_MODEL ?? 'gpt-4';
      const xProxySource = process.env.TIMEWEB_X_PROXY_SOURCE;

      if (!agentAccessId || !apiKey) return null;

      const config: Record<string, string> = {
        baseURL,
        agentAccessId,
        apiKey,
        model,
        ...(xProxySource && { xProxySource }),
      };
      return {
        id: 'env-timeweb',
        name: 'Timeweb Cloud AI',
        type: 'timeweb',
        chatModels: [{ key: model, name: model }],
        embeddingModels: [],
        config,
        hash: hashObj({ baseURL, agentAccessId }),
      };
    }

    return null;
  }

  public getModelProviders(): ConfigModelProvider[] {
    return JSON.parse(JSON.stringify(this.currentConfig.modelProviders));
  }

  public getModelProviderById(id: string): ConfigModelProvider | undefined {
    return this.currentConfig.modelProviders.find((p) => p.id === id);
  }

  public addModelProvider(
    type: string,
    name: string,
    config: Record<string, unknown>,
  ): ConfigModelProvider {
    const newProvider: ConfigModelProvider = {
      id: crypto.randomUUID(),
      name,
      type,
      config,
      chatModels: [],
      embeddingModels: [],
      hash: hashObj(config),
    };
    this.currentConfig.modelProviders.push(newProvider);
    this.save();
    return newProvider;
  }

  public removeModelProvider(id: string): void {
    this.currentConfig.modelProviders =
      this.currentConfig.modelProviders.filter((p) => p.id !== id);
    this.save();
  }

  public updateModelProvider(
    id: string,
    name: string,
    config: Record<string, unknown>,
  ): ConfigModelProvider {
    const provider = this.currentConfig.modelProviders.find((p) => p.id === id);
    if (!provider) throw new Error('Provider not found');
    provider.name = name;
    provider.config = config;
    provider.hash = hashObj(config);
    this.save();
    return provider;
  }

  public addProviderModel(
    providerId: string,
    type: 'embedding' | 'chat',
    model: { key: string; name: string },
  ): void {
    const provider = this.currentConfig.modelProviders.find(
      (p) => p.id === providerId,
    );
    if (!provider) throw new Error('Invalid provider id');
    if (type === 'chat') {
      provider.chatModels.push(model);
    } else {
      provider.embeddingModels.push(model);
    }
    this.save();
  }

  public removeProviderModel(
    providerId: string,
    type: 'embedding' | 'chat',
    modelKey: string,
  ): void {
    const provider = this.currentConfig.modelProviders.find(
      (p) => p.id === providerId,
    );
    if (!provider) throw new Error('Invalid provider id');
    if (type === 'chat') {
      provider.chatModels = provider.chatModels.filter((m) => m.key !== modelKey);
    } else {
      provider.embeddingModels = provider.embeddingModels.filter(
        (m) => m.key !== modelKey,
      );
    }
    this.save();
  }
}

export const providersConfig = new ProvidersConfig();
