const { SystemSettings } = require("../models/systemSettings");
const {
  flexUserRoleValid,
  ROLES,
} = require("../utils/middleware/multiUserProtected");
const { validatedRequest } = require("../utils/middleware/validatedRequest");
const {
  validExportTypes,
  sendChatHistoryFile,
} = require("../utils/chats/exportChatToFile");
const { multiUserMode, reqBody, userFromSession } = require("../utils/http");

function utilEndpoints(app) {
  if (!app) return;

  app.get("/utils/metrics", async (_, response) => {
    try {
      const metrics = {
        online: true,
        version: getGitVersion(),
        mode: (await SystemSettings.isMultiUserMode())
          ? "multi-user"
          : "single-user",
        vectorDB: process.env.VECTOR_DB || "lancedb",
        storage: await getDiskStorage(),
        appVersion: getDeploymentVersion(),
      };
      response.status(200).json(metrics);
    } catch (e) {
      console.error(e);
      response.sendStatus(500).end();
    }
  });

  app.post(
    "/export-chat/:type",
    [validatedRequest, flexUserRoleValid([ROLES.all])],
    async (request, response) => {
      try {
        const { type } = request.params;
        if (!validExportTypes.includes(type))
          return response.sendStatus(400).end();

        const { workspaceSlug, threadSlug } = reqBody(request);
        const { Workspace } = require("../models/workspace");
        const { WorkspaceThread } = require("../models/workspaceThread");
        const { WorkspaceChats } = require("../models/workspaceChats");

        const user = await userFromSession(request, response);
        const workspace = multiUserMode(response)
          ? await Workspace.getWithUser(user, { slug: String(workspaceSlug) })
          : await Workspace.get({ slug: String(workspaceSlug) });
        if (!workspace) return response.sendStatus(404).end();

        let thread;
        if (threadSlug) {
          thread = await WorkspaceThread.get({
            slug: String(threadSlug),
            user_id: user?.id || null,
          });
          if (!thread) return response.sendStatus(404).end();
        }

        const chats = await WorkspaceChats.where({
          workspaceId: workspace.id,
          user_id: user?.id || null,
          thread_id: thread?.id || null,
        });
        if (chats.length === 0) return response.sendStatus(400).end();

        const meta = {
          workspaceName: workspace.name,
          threadName: thread?.name || null,
        };

        return sendChatHistoryFile(response, chats, meta, type);
      } catch (e) {
        console.error(e);
        response.sendStatus(500).end();
      }
    }
  );

  const {
    dockerModelRunnerUtilsEndpoints,
  } = require("./utils/dockerModelRunnerUtils");
  dockerModelRunnerUtilsEndpoints(app);

  const { lemonadeUtilsEndpoints } = require("./utils/lemonadeUtilsEndpoints");
  lemonadeUtilsEndpoints(app);
}

function getGitVersion() {
  if (process.env.ANYTHING_LLM_RUNTIME === "docker") return "--";
  try {
    return require("child_process")
      .execSync("git rev-parse HEAD")
      .toString()
      .trim();
  } catch (e) {
    console.error("getGitVersion", e.message);
    return "--";
  }
}

function byteToGigaByte(n) {
  return n / Math.pow(10, 9);
}

async function getDiskStorage() {
  try {
    const checkDiskSpace = require("check-disk-space").default;
    const { free, size } = await checkDiskSpace("/");
    return {
      current: Math.floor(byteToGigaByte(free)),
      capacity: Math.floor(byteToGigaByte(size)),
    };
  } catch {
    return {
      current: null,
      capacity: null,
    };
  }
}

/**
 * Returns the model tag based on the provider set in the environment.
 * This information is used to identify the parent model for the system
 * so that we can prioritize the correct model and types for future updates
 * as well as build features in AnythingLLM directly for a specific model or capabilities.
 *
 * Disable with  {@link https://github.com/Mintplex-Labs/anything-llm?tab=readme-ov-file#telemetry--privacy|Disable Telemetry}
 * @returns {string} The model tag.
 */
function getModelTag() {
  let model = null;
  const provider = process.env.LLM_PROVIDER;

  switch (provider) {
    case "openai":
      model = process.env.OPEN_MODEL_PREF;
      break;
    case "anthropic":
      model = process.env.ANTHROPIC_MODEL_PREF;
      break;
    case "lmstudio":
      model = process.env.LMSTUDIO_MODEL_PREF;
      break;
    case "ollama":
      model = process.env.OLLAMA_MODEL_PREF;
      break;
    case "groq":
      model = process.env.GROQ_MODEL_PREF;
      break;
    case "togetherai":
      model = process.env.TOGETHER_AI_MODEL_PREF;
      break;
    case "azure":
      model =
        process.env.AZURE_OPENAI_MODEL_PREF || process.env.OPEN_MODEL_PREF;
      break;
    case "koboldcpp":
      model = process.env.KOBOLD_CPP_MODEL_PREF;
      break;
    case "localai":
      model = process.env.LOCAL_AI_MODEL_PREF;
      break;
    case "openrouter":
      model = process.env.OPENROUTER_MODEL_PREF;
      break;
    case "mistral":
      model = process.env.MISTRAL_MODEL_PREF;
      break;
    case "generic-openai":
      model = process.env.GENERIC_OPEN_AI_MODEL_PREF;
      break;
    case "perplexity":
      model = process.env.PERPLEXITY_MODEL_PREF;
      break;
    case "textgenwebui":
      model = "textgenwebui-default";
      break;
    case "bedrock":
      model = process.env.AWS_BEDROCK_LLM_MODEL_PREFERENCE;
      break;
    case "fireworksai":
      model = process.env.FIREWORKS_AI_LLM_MODEL_PREF;
      break;
    case "deepseek":
      model = process.env.DEEPSEEK_MODEL_PREF;
      break;
    case "litellm":
      model = process.env.LITE_LLM_MODEL_PREF;
      break;
    case "apipie":
      model = process.env.APIPIE_LLM_MODEL_PREF;
      break;
    case "xai":
      model = process.env.XAI_LLM_MODEL_PREF;
      break;
    case "novita":
      model = process.env.NOVITA_LLM_MODEL_PREF;
      break;
    case "nvidia-nim":
      model = process.env.NVIDIA_NIM_LLM_MODEL_PREF;
      break;
    case "ppio":
      model = process.env.PPIO_MODEL_PREF;
      break;
    case "gemini":
      model = process.env.GEMINI_LLM_MODEL_PREF;
      break;
    case "moonshotai":
      model = process.env.MOONSHOT_AI_MODEL_PREF;
      break;
    case "zai":
      model = process.env.ZAI_MODEL_PREF;
      break;
    case "giteeai":
      model = process.env.GITEE_AI_MODEL_PREF;
      break;
    case "cohere":
      model = process.env.COHERE_MODEL_PREF;
      break;
    case "docker-model-runner":
      model = process.env.DOCKER_MODEL_RUNNER_LLM_MODEL_PREF;
      break;
    case "privatemode":
      model = process.env.PRIVATEMODE_LLM_MODEL_PREF;
      break;
    case "sambanova":
      model = process.env.SAMBANOVA_LLM_MODEL_PREF;
      break;
    case "lemonade":
      model = process.env.LEMONADE_LLM_MODEL_PREF;
      break;
    case "minimax":
      model = process.env.MINIMAX_MODEL_PREF;
      break;
    case "cerebras":
      model = process.env.CEREBRAS_MODEL_PREF;
      break;
    default:
      model = "--";
      break;
  }
  return model;
}

/**
 * Returns the deployment version.
 * - Dev: reads from package.json
 * - Prod: reads from ENV
 * expected format: major.minor.patch
 * @returns {string|null} The deployment version.
 */
function getDeploymentVersion() {
  if (process.env.NODE_ENV === "development")
    return require("../../package.json").version;
  if (process.env.DEPLOYMENT_VERSION) return process.env.DEPLOYMENT_VERSION;
  return null;
}

/**
 * Returns the user agent for the AnythingLLM deployment.
 * @returns {string} The user agent.
 */
function getAnythingLLMUserAgent() {
  const version = getDeploymentVersion() || "unknown";
  return `AnythingLLM/${version}`;
}

module.exports = {
  utilEndpoints,
  getGitVersion,
  getModelTag,
  getAnythingLLMUserAgent,
  getDeploymentVersion,
};
