// src/services/ai.ts
// Copyright (C) 2025 DTP Technologies, LLC
// All Rights Reserved

import {
  AiProvider,
  type ApiType,
  type IAiModelSettings,
} from "../models/ai-provider.js";
import { User } from "../models/user.js";
import { OllamaAiClient } from "../lib/ai-clients/ollama-client.js";
import { OpenAiClient } from "../lib/ai-clients/openai-client.js";
import type { AiClient, ChatOptions } from "../lib/ai-client.js";

import { DtpService } from "../lib/service.js";

interface ProviderData {
  apiType: ApiType;
  baseUrl: string;
  apiKey: string;
}

class AiService extends DtpService {
  get name(): string {
    return "AiService";
  }

  get slug(): string {
    return "ai";
  }

  constructor() {
    super();
  }

  async start(): Promise<void> {
    this.log.info("service started");
  }

  async stop(): Promise<void> {
    this.log.info("service stopped");
  }

  getClient(provider: ProviderData): AiClient {
    if (provider.apiType === "ollama") {
      return new OllamaAiClient(provider.baseUrl, provider.apiKey);
    }
    return new OpenAiClient(provider.baseUrl, provider.apiKey);
  }

  async getAgentClient(userId: string): Promise<AiClient> {
    const user = await User.findById(userId)
      .select("+connections.ai.agentProviderId +connections.ai.agentModel")
      .lean();
    if (!user) {
      throw new Error("User not found");
    }

    const aiConfig = user.connections?.ai;
    if (!aiConfig?.agentProviderId) {
      throw new Error("No agent provider configured");
    }

    const provider = await AiProvider.findById(aiConfig.agentProviderId)
      .select("+apiKey")
      .lean();
    if (!provider) {
      throw new Error("Agent provider not found");
    }

    return this.getClient({
      apiType: provider.apiType,
      baseUrl: provider.baseUrl,
      apiKey: provider.apiKey,
    });
  }

  async getAgentProviderInfo(
    userId: string,
  ): Promise<{ providerId: string; model: string } | null> {
    const user = await User.findById(userId)
      .select("+connections.ai.agentProviderId +connections.ai.agentModel")
      .lean();
    if (!user) {
      return null;
    }

    const aiConfig = user.connections?.ai;
    if (!aiConfig?.agentProviderId || !aiConfig?.agentModel) {
      return null;
    }

    return {
      providerId: aiConfig.agentProviderId.toString(),
      model: aiConfig.agentModel,
    };
  }

  async getVectorClient(userId: string): Promise<AiClient> {
    const user = await User.findById(userId)
      .select("+connections.ai.vectorProviderId +connections.ai.vectorModel")
      .lean();
    if (!user) {
      throw new Error("User not found");
    }

    const aiConfig = user.connections?.ai;
    if (!aiConfig?.vectorProviderId) {
      throw new Error("No vector provider configured");
    }

    const provider = await AiProvider.findById(aiConfig.vectorProviderId)
      .select("+apiKey")
      .lean();
    if (!provider) {
      throw new Error("Vector provider not found");
    }

    return this.getClient({
      apiType: provider.apiType,
      baseUrl: provider.baseUrl,
      apiKey: provider.apiKey,
    });
  }

  async getUtilityClient(userId: string): Promise<AiClient> {
    const user = await User.findById(userId)
      .select("+connections.ai.utilityProviderId +connections.ai.utilityModel")
      .lean();
    if (!user) {
      throw new Error("User not found");
    }

    const aiConfig = user.connections?.ai;
    if (!aiConfig?.utilityProviderId) {
      throw new Error("No utility provider configured");
    }

    const provider = await AiProvider.findById(aiConfig.utilityProviderId)
      .select("+apiKey")
      .lean();
    if (!provider) {
      throw new Error("Utility provider not found");
    }

    return this.getClient({
      apiType: provider.apiType,
      baseUrl: provider.baseUrl,
      apiKey: provider.apiKey,
    });
  }

  async getAgentModel(userId: string): Promise<string> {
    const user = await User.findById(userId)
      .select("+connections.ai.agentModel")
      .lean();
    if (!user) {
      throw new Error("User not found");
    }
    return user.connections?.ai?.agentModel || "";
  }

  async getVectorModel(userId: string): Promise<string> {
    const user = await User.findById(userId)
      .select("+connections.ai.vectorModel")
      .lean();
    if (!user) {
      throw new Error("User not found");
    }
    return user.connections?.ai?.vectorModel || "";
  }

  async getUtilityModel(userId: string): Promise<string> {
    const user = await User.findById(userId)
      .select("+connections.ai.utilityModel")
      .lean();
    if (!user) {
      throw new Error("User not found");
    }
    return user.connections?.ai?.utilityModel || "";
  }

  async getModelSettings(
    userId: string,
    providerId: string,
    modelId: string,
  ): Promise<IAiModelSettings | null> {
    const provider = await AiProvider.findOne({
      _id: providerId,
      user: userId,
    }).lean();

    if (!provider) {
      return null;
    }

    const model = provider.models.find((m) => m.id === modelId);
    return model?.settings ?? null;
  }

  async getModelChatOptions(
    userId: string,
    providerId: string,
    modelId: string,
  ): Promise<ChatOptions> {
    const provider = await AiProvider.findOne({
      _id: providerId,
      user: userId,
    }).lean();

    if (!provider) {
      return {};
    }

    const model = provider.models.find((m) => m.id === modelId);
    const settings = model?.settings;

    if (!settings) {
      const defaults = this.getDefaultOptions(
        provider.apiType,
        model?.contextWindow,
      );
      return defaults;
    }

    return {
      temperature: settings.temperature,
      topP: settings.topP,
      topK: settings.topK,
      numCtx: settings.numCtx,
    };
  }

  private getDefaultOptions(
    apiType: ApiType,
    contextWindow?: number,
  ): ChatOptions {
    if (apiType === "ollama") {
      return {
        temperature: 0.8,
        topP: 0.9,
        topK: 40,
        numCtx: contextWindow || 2048,
      };
    }
    return {
      temperature: 0.7,
      topP: 1.0,
    };
  }

  async isConfigured(userId: string): Promise<boolean> {
    const user = await User.findById(userId)
      .select(
        "+connections.ai.agentProviderId +connections.ai.vectorProviderId +connections.ai.utilityProviderId",
      )
      .lean();
    if (!user) {
      return false;
    }

    const aiConfig = user.connections?.ai;
    return !!(
      aiConfig?.agentProviderId &&
      aiConfig?.vectorProviderId &&
      aiConfig?.utilityProviderId
    );
  }

  async getModelDisplay(
    userId: string,
    slot: "agent" | "vector" | "util",
  ): Promise<string> {
    const user = await User.findById(userId)
      .select(
        "+connections.ai.agentModel +connections.ai.agentProviderId +connections.ai.vectorModel +connections.ai.vectorProviderId +connections.ai.utilityModel +connections.ai.utilityProviderId",
      )
      .lean();
    if (!user) return "N/A";

    const aiConfig = user.connections?.ai;
    let model = "";
    let providerId: string | null = null;

    if (slot === "agent") {
      model = aiConfig?.agentModel || "";
      providerId = aiConfig?.agentProviderId?.toString() || null;
    } else if (slot === "vector") {
      model = aiConfig?.vectorModel || "";
      providerId = aiConfig?.vectorProviderId?.toString() || null;
    } else {
      model = aiConfig?.utilityModel || "";
      providerId = aiConfig?.utilityProviderId?.toString() || null;
    }

    if (!model || !providerId) return "N/A";

    const provider = await AiProvider.findById(providerId)
      .select("name")
      .lean();
    const providerName = provider?.name || "Unknown";

    return `${model} (${providerName})`;
  }
}

export default new AiService();
