import { createMutation } from "@tanstack/solid-query";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { onOpenUrl } from "@tauri-apps/plugin-deep-link";
import * as shell from "@tauri-apps/plugin-shell";
import { z } from "zod";
import callbackTemplate from "~/components/callback.template";
import { authStore, generalSettingsStore } from "~/store";
import { identifyUser, trackEvent } from "./analytics";
import { commands } from "./tauri";

export function createSignInMutation() {
  return createMutation(() => ({
    mutationFn: async (abort: AbortController) => {
      const platform = import.meta.env.DEV ? "web" : "desktop";

      let session;

      if (platform === "web")
        session = await createLocalServerSession(abort.signal);
      else session = await createDeepLinkSession(abort.signal);

      await shell.open(session.url.toString());

      await processAuthData(await session.complete());

      getCurrentWindow().setFocus();
    },
  }));
}

async function createSessionRequestUrl(
  port: string | null,
  platform: "web" | "desktop"
) {
  const serverUrl =
    (await generalSettingsStore.get())?.serverUrl ?? "https://cap.so";
  const callbackUrl = new URL(
    `/api/desktop/session/request?type=api_key`,
    serverUrl
  );

  if (port !== null) callbackUrl.searchParams.set("port", port);
  callbackUrl.searchParams.set("platform", platform);

  return callbackUrl;
}

async function createLocalServerSession(signal: AbortSignal) {
  await invoke("plugin:oauth|stop").catch(() => {});

  const port: string = await invoke("plugin:oauth|start", {
    config: {
      response: callbackTemplate,
      headers: {
        "Content-Type": "text/html; charset=utf-8",
        "Cache-Control": "no-store, no-cache, must-revalidate",
        Pragma: "no-cache",
      },
      // Add a cleanup function to stop the server after handling the request
      cleanup: true,
    },
  });

  signal.onabort = () => {
    invoke("plugin:oauth|stop").catch(() => {});
  };

  let res: (url: URL) => void;
  const stopListening = await listen(
    "oauth://url",
    (data: { payload: string }) => {
      console.log(data);
      if (
        !(data.payload.includes("token") || data.payload.includes("api_key"))
      ) {
        return;
      }

      const urlObject = new URL(data.payload);
      res(urlObject);
    }
  );

  return {
    url: await createSessionRequestUrl(port, "web"),
    complete: async () => {
      const url = await new Promise<URL>((r) => {
        res = r;
      });

      console.log(url);
      stopListening();

      if (signal.aborted) throw new Error("Sign in aborted");

      const a = [...url.searchParams].reduce((acc, [k, v]) => {
        acc[k] = v;
        return acc;
      }, {} as any);

      return paramsValidator.parse(a);
    },
  };
}

const paramsValidator = z.union([
  z.object({
    type: z.literal("api_key"),
    api_key: z.string(),
    user_id: z.string(),
  }),
  z.object({
    token: z.string(),
    user_id: z.string(),
    expires: z.coerce.number(),
  }),
]);

async function createDeepLinkSession(signal: AbortSignal) {
  let res: (data: z.infer<typeof paramsValidator>) => void;
  const p = new Promise<z.infer<typeof paramsValidator>>((r) => {
    res = r;
  });
  const stopListening = await onOpenUrl(async (urls) => {
    for (const urlString of urls) {
      if (signal.aborted) return;

      const url = new URL(urlString);

      res(
        paramsValidator.parse(
          [...url.searchParams].reduce((acc, [k, v]) => {
            acc[k] = v;
            return acc;
          }, {} as any)
        )
      );
    }
  });

  signal.onabort = () => {
    stopListening();
  };

  return {
    url: await createSessionRequestUrl(null, "desktop"),
    complete: () => p,
  };
}

async function processAuthData(data: z.infer<typeof paramsValidator>) {
  identifyUser(data.user_id);
  trackEvent("user_signed_in", { platform: "desktop" });

  const existingAuth = await authStore.get();
  await authStore.set({
    secret:
      "api_key" in data
        ? { api_key: data.api_key }
        : { token: data.token, expires: data.expires },
    user_id: data.user_id,
    intercom_hash: existingAuth?.intercom_hash ?? "",
    plan: null,
  });

  await commands.updateAuthPlan();
}
