import z from "zod/v4";
export const isValidJson = (value: string): boolean => {
  try {
    JSON.parse(value);
    return true;
  } catch {
    return false;
  }
};

export const remoteUrlSchema = z.string().superRefine((val, ctx) => {
  if (!val) {
    ctx.addIssue({ code: z.ZodIssueCode.custom, message: "URL is required" });
    return;
  }
  let parsed: URL;
  try {
    parsed = new URL(val);
  } catch {
    ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Invalid URL" });
    return;
  }
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: "URL must use http or https",
    });
  }
});

export const serverNameSchema = z
  .string()
  .min(1, "Server name is required")
  .regex(
    /^[a-zA-Z0-9_-]+$/,
    "Server name can only contain letters, numbers, dashes (-), and underscores (_)",
  );

export const envValueSchema = z.union([
  z.string(),
  z.object({ fromEnv: z.string() }),
  z.object({ fromSecret: z.string() }),
  z.null(),
]);

export const localServerSchema = z.strictObject({
  type: z.literal("stdio").default("stdio").optional(),
  command: z.string().min(1, "Command is required"),
  args: z.array(z.string()).default([]).optional(),
  env: z.record(z.string().min(1), envValueSchema).default({}).optional(),
  icon: z.string().optional(),
});

export const remoteServerSchema = z.strictObject({
  type: z.enum(["sse", "streamable-http", "http"]).default("sse").optional(),
  url: remoteUrlSchema,
  headers: z.record(z.string(), envValueSchema).optional(),
  icon: z.string().optional(),
});

export const mcpServerSchema = z.union([localServerSchema, remoteServerSchema]);

export const mcpJsonSchema = z.record(serverNameSchema, mcpServerSchema);

// The payload schemas are not strict, so they are used mainly to strip irrelevant properties,
// and do transforms that are not possible with the JSON Schemas, but not for validation.
const localServerPayloadObject = z.object({
  type: z.literal("stdio").default("stdio"),
  command: z.string(),
  name: z.string(),
  args: z.array(z.string()).optional().default([]),
  env: z.record(z.string(), envValueSchema).optional().default({}),
  icon: z.string().optional(),
});

export const localServerPayloadSchema = localServerPayloadObject.transform(
  (server: z.output<typeof localServerPayloadObject>) => ({
    ...server,
    type: server.type || "stdio",
  }),
);
export const remoteServerPayloadSchema = z.object({
  icon: z.string().optional(),
  name: z.string(),
  type: z.enum(["sse", "streamable-http", "http"]).default("sse").optional(),
  url: z.string(),
  headers: z.record(z.string(), envValueSchema).optional(),
});

export const parseServerPayload = (
  server: z.input<typeof mcpServerSchema>,
): z.ZodSafeParseResult<
  | z.infer<typeof localServerPayloadSchema>
  | z.infer<typeof remoteServerPayloadSchema>
> => {
  if (server.type === "stdio") {
    return localServerPayloadSchema.safeParse(server);
  }
  // support http servers for UI compatibility
  if (server.type === "http") {
    return remoteServerPayloadSchema.safeParse({
      ...server,
      type: "streamable-http",
    });
  }
  // Only set type if 'url' exists
  if ("url" in server && typeof server.url === "string") {
    const inferredType = inferServerTypeFromUrl(server.url);
    // Ensure type is always set and matches expected union
    if (inferredType === "sse") {
      return remoteServerPayloadSchema.safeParse({ ...server, type: "sse" });
    } else {
      return remoteServerPayloadSchema.safeParse({
        ...server,
        type: "streamable-http",
      });
    }
  }
  return remoteServerPayloadSchema.safeParse(server);
};

/**
 * Maps loose MCP JSON (`mcpJsonSchema`, one object per server name) to the
 * strict target-server shape used by space config / hub (explicit `type`, no
 * `name` on each value — the record key is the name).
 *
 * Mirrors validateAndProcessServer in server-helpers: default `type`,
 * coerce `http` → `streamable-http`, then parseServerPayload.
 */
export function normalizeMcpJsonRecordToSpaceTargetServers(
  raw: unknown,
):
  | { success: true; data: Record<string, Record<string, unknown>> }
  | { success: false; error: string } {
  const mcpParsed = mcpJsonSchema.safeParse(raw);
  if (!mcpParsed.success) {
    return { success: false, error: z.prettifyError(mcpParsed.error) };
  }

  const out: Record<string, Record<string, unknown>> = {};

  for (const [serverName, serverConfig] of Object.entries(mcpParsed.data)) {
    const payload: Record<string, unknown> = {
      ...(serverConfig as Record<string, unknown>),
      name: serverName,
    };

    if (payload["type"] === "http") {
      payload["type"] = "streamable-http";
    }

    const parseResult = parseServerPayload(
      payload as z.input<typeof mcpServerSchema> & { name: string },
    );
    if (!parseResult.success) {
      return {
        success: false,
        error: `${serverName}: ${z.prettifyError(parseResult.error)}`,
      };
    }

    const data = parseResult.data as Record<string, unknown> & {
      name: string;
    };
    const { name: _omitName, ...target } = data;
    out[serverName] = target;
  }

  return { success: true, data: out };
}

export function isRemoteUrlValid(url: string): boolean {
  try {
    const { protocol } = new URL(url);
    return protocol === "http:" || protocol === "https:";
  } catch {
    return false;
  }
}

export const inferServerTypeFromUrl = (
  url: string,
): "sse" | "streamable-http" | undefined => {
  try {
    const urlObj = new URL(url);
    const pathname = urlObj.pathname.replace(/\/$/, "");
    if (pathname.endsWith("/sse")) {
      return "sse";
    } else if (pathname.endsWith("/mcp")) {
      return "streamable-http";
    }
    return undefined;
  } catch {
    return undefined;
  }
};

/**
 * Updates JSON content to include the inferred server type.
 * Also strips the `icon` field if present in the input JSON since it's a UI-only concern.
 */
export const updateJsonWithServerType = (
  jsonContent: string,
  serverName: string,
): string => {
  try {
    const parsed = JSON.parse(jsonContent);
    const serverData = parsed[serverName];

    if (!serverData) {
      return jsonContent;
    }

    // Determine server type
    let serverType: string;
    if (serverData.type) {
      serverType = serverData.type;
    } else if ("url" in serverData) {
      serverType = inferServerTypeFromUrl(serverData.url) || "sse";
      if (serverType === "http") {
        serverType = "streamable-http";
      }
    } else {
      serverType = "stdio";
    }

    const { icon: _stripIcon, ...serverDataWithoutIcon } = serverData;
    const updatedJson = {
      [serverName]: {
        ...serverDataWithoutIcon,
        type: serverType,
      },
    };

    return JSON.stringify(updatedJson, null, 2);
  } catch (error) {
    console.warn("Failed to update JSON with server type:", error);
    return jsonContent;
  }
};
