import { RouteId, SupportedProvidersSchema } from "@archestra/shared";
import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
import { z } from "zod";
import { getEmailProviderInfo } from "@/agents/incoming-email";
import { anthropicWorkloadIdentity } from "@/clients/anthropic-workload-identity";
import { isAzureOpenAiEntraIdEnabled } from "@/clients/azure-openai-credentials";
import { isBedrockIamAuthEnabled } from "@/clients/bedrock-credentials";
import { isVertexAiEnabled } from "@/clients/gemini-client";
import config from "@/config";
import { enterpriseTier } from "@/enterprise-tier";
import { McpServerRuntimeManager } from "@/k8s/mcp-server-runtime";
import logger from "@/logging";
import { OrganizationModel } from "@/models";
import { ngrokTunnelManager } from "@/ngrok-tunnel-manager";
import { getByosVaultKvVersion, isByosEnabled } from "@/secrets-manager";
import { skillSandboxRuntimeService } from "@/skills-sandbox/skill-sandbox-runtime-service";
import { EmailProviderTypeSchema } from "@/types";
import { PUBLIC_CONFIG_PATH } from "./route-paths";

export const publicConfigRoutes: FastifyPluginAsyncZod = async (fastify) => {
  fastify.get(
    PUBLIC_CONFIG_PATH,
    {
      schema: {
        operationId: RouteId.GetPublicConfig,
        description: "Get public config",
        tags: ["Config"],
        response: {
          200: PublicConfigResponseSchema,
        },
      },
    },
    async (_request, reply) => {
      return reply.send(await getPublicConfigResponse());
    },
  );
};

const configRoutes: FastifyPluginAsyncZod = async (fastify) => {
  await fastify.register(publicConfigRoutes);

  fastify.get(
    "/api/config",
    {
      schema: {
        operationId: RouteId.GetConfig,
        description: "Get platform configuration and feature flags",
        tags: ["Config"],
        response: {
          200: z.strictObject({
            enterpriseFeatures: z.strictObject({
              core: z.boolean(),
              knowledgeBase: z.boolean(),
              fullWhiteLabeling: z.boolean(),
            }),
            smallTeamTier: z.strictObject({
              threshold: z.number(),
              userCount: z.number(),
              smallTeam: z.boolean(),
              envFlag: z.boolean(),
              communicate: z.boolean(),
            }),
            features: z.strictObject({
              betaEnabled: z.boolean(),
              orchestratorK8sRuntime: z.boolean(),
              sandbox: z.boolean(),
              // Max size of a file the sandbox can stage. The chat composer caps
              // sandbox-routed uploads at this instead of guessing.
              sandboxArtifactBytesLimit: z.number(),
              byosEnabled: z.boolean(),
              byosVaultKvVersion: z.enum(["1", "2"]).nullable(),
              azureOpenAiEntraIdEnabled: z.boolean(),
              anthropicWifEnabled: z.boolean(),
              bedrockIamAuthEnabled: z.boolean(),
              geminiVertexAiEnabled: z.boolean(),
              incomingEmail: z.object({
                enabled: z.boolean(),
                provider: EmailProviderTypeSchema.optional(),
                displayName: z.string().optional(),
                emailDomain: z.string().optional(),
              }),
              mcpServerBaseImage: z.string(),
              orchestratorK8sNamespace: z.string(),
              environmentNamespaces: z.array(z.string()),
              isQuickstart: z.boolean(),
              ngrokDomain: z.string(),
              virtualKeyDefaultExpirationSeconds: z.number(),
              mcpSandboxDomain: z.string().nullable(),
              maintenanceMode: z.string().nullable(),
              chatSecretScanEnabled: z.boolean(),
              agentHooksEnabled: z.boolean(),
            }),
            providerBaseUrls: z.record(
              SupportedProvidersSchema,
              z.string().nullable(),
            ),
          }),
        },
      },
    },
    async (_request, reply) => {
      const tier = enterpriseTier.getState();

      return reply.send({
        enterpriseFeatures: {
          core: tier.coreActive,
          knowledgeBase: tier.knowledgeBaseActive,
          fullWhiteLabeling: config.enterpriseFeatures.fullWhiteLabeling,
        },
        smallTeamTier: {
          threshold: tier.threshold,
          userCount: tier.userCount,
          smallTeam: tier.smallTeam,
          envFlag: tier.envFlag,
          communicate: tier.communicate,
        },
        features: {
          betaEnabled: config.beta,
          orchestratorK8sRuntime: McpServerRuntimeManager.isEnabled,
          sandbox: skillSandboxRuntimeService.isEnabled,
          sandboxArtifactBytesLimit: config.skillsSandbox.artifactBytesLimit,
          byosEnabled: isByosEnabled(),
          byosVaultKvVersion: getByosVaultKvVersion(),
          azureOpenAiEntraIdEnabled: isAzureOpenAiEntraIdEnabled(),
          anthropicWifEnabled: anthropicWorkloadIdentity.isEnabled(),
          bedrockIamAuthEnabled: isBedrockIamAuthEnabled(),
          geminiVertexAiEnabled: isVertexAiEnabled(),
          incomingEmail: getEmailProviderInfo(),
          mcpServerBaseImage: config.orchestrator.mcpServerBaseImage,
          orchestratorK8sNamespace: config.orchestrator.kubernetes.namespace,
          environmentNamespaces:
            config.orchestrator.kubernetes.environmentNamespaces,
          isQuickstart: config.isQuickstart,
          ngrokDomain: ngrokTunnelManager.getPublicDomain(),
          virtualKeyDefaultExpirationSeconds:
            config.llmProxy.virtualKeyDefaultExpirationSeconds,
          mcpSandboxDomain: config.mcpSandbox.domain,
          maintenanceMode: config.maintenanceMode,
          chatSecretScanEnabled: config.chat.secretScanEnabled,
          agentHooksEnabled: config.hooks.enabled,
        },
        providerBaseUrls: {
          openai: config.llm.openai.baseUrl || null,
          openrouter: config.llm.openrouter.baseUrl || null,
          anthropic: config.llm.anthropic.baseUrl || null,
          gemini: config.llm.gemini.baseUrl || null,
          bedrock: config.llm.bedrock.baseUrl || null,
          cohere: config.llm.cohere.baseUrl || null,
          cerebras: config.llm.cerebras.baseUrl || null,
          mistral: config.llm.mistral.baseUrl || null,
          perplexity: config.llm.perplexity.baseUrl || null,
          groq: config.llm.groq.baseUrl || null,
          xai: config.llm.xai.baseUrl || null,
          vllm: config.llm.vllm.baseUrl || null,
          ollama: config.llm.ollama.baseUrl || null,
          zhipuai: config.llm.zhipuai.baseUrl || null,
          minimax: config.llm.minimax.baseUrl || null,
          deepseek: config.llm.deepseek.baseUrl || null,
          "github-copilot": config.llm["github-copilot"].baseUrl || null,
          azure: config.llm.azure.baseUrl || null,
        },
      });
    },
  );
};

export default configRoutes;

const PublicConfigResponseSchema = z.strictObject({
  disableBasicAuth: z.boolean(),
  disableInvitations: z.boolean(),
  // Developer-only: when true, the login page auto-mints a session via
  // POST /api/auth/dev-auto-login instead of showing the sign-in form. Always
  // false in production (the driving env var is ignored there).
  devAutoLoginEnabled: z.boolean(),
  maintenanceMode: z.string().nullable(),
  // Instance-wide banner (markdown) rendered at the top of the UI. Exposed
  // pre-auth so operators can label an instance on the login screen too.
  siteNotificationMessage: z.string().nullable(),
  // Effective enterprise core flag (env var OR small-team free tier). Exposed
  // pre-auth so the login screen can decide whether to render the SSO picker.
  enterpriseCoreActive: z.boolean(),
  analytics: z.strictObject({
    enabled: z.boolean(),
    instanceId: z.string().uuid().nullable(),
    posthog: z.strictObject({
      key: z.string(),
      host: z.string(),
    }),
  }),
});

let cachedAnalyticsInstanceId: string | null = null;
let pendingAnalyticsInstanceId: Promise<string | null> | null = null;
let hasLoggedAnalyticsInstanceIdError = false;

async function getPublicConfigResponse(): Promise<
  z.infer<typeof PublicConfigResponseSchema>
> {
  return {
    disableBasicAuth: config.auth.disableBasicAuth,
    disableInvitations: config.auth.disableInvitations,
    devAutoLoginEnabled: !!config.auth.devAutoAuthenticateEmail,
    maintenanceMode: config.maintenanceMode,
    siteNotificationMessage: config.siteNotificationMessage,
    enterpriseCoreActive: enterpriseTier.isCoreActive(),
    analytics: {
      enabled: config.analytics.enabled,
      instanceId: await getAnalyticsInstanceId(),
      posthog: config.analytics.posthog,
    },
  };
}

async function getAnalyticsInstanceId(): Promise<string | null> {
  if (config.maintenanceMode) return null;
  if (cachedAnalyticsInstanceId) return cachedAnalyticsInstanceId;

  pendingAnalyticsInstanceId ??= loadAnalyticsInstanceId();
  try {
    return await pendingAnalyticsInstanceId;
  } finally {
    pendingAnalyticsInstanceId = null;
  }
}

async function loadAnalyticsInstanceId(): Promise<string | null> {
  try {
    const instanceId = (await OrganizationModel.getAnalyticsState())
      .analyticsInstanceId;
    cachedAnalyticsInstanceId = instanceId;
    hasLoggedAnalyticsInstanceIdError = false;
    return instanceId;
  } catch (error) {
    if (!hasLoggedAnalyticsInstanceIdError) {
      logger.warn(
        { err: error },
        "Failed to load analytics instance ID for public config",
      );
      hasLoggedAnalyticsInstanceIdError = true;
    }
    return null;
  }
}
