"use server";

import {
  createPasskeyRegistrationLink,
  getLoginSettings,
  getSession,
  getUserByID,
  registerPasskey,
  verifyPasskeyRegistration as zitadelVerifyPasskeyRegistration,
} from "@/lib/zitadel";
import { create, Duration } from "@zitadel/client";
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import {
  RegisterPasskeyResponse,
  VerifyPasskeyRegistrationRequestSchema,
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { headers } from "next/headers";
import { userAgent } from "next/server";
import { getNextUrl } from "../client";
import {
  getMostRecentSessionCookie,
  getSessionCookieById,
  getSessionCookieByLoginName,
} from "../cookies";
import { checkEmailVerification } from "../verify-helper";
import { setSessionAndUpdateCookie } from "./cookie";

type VerifyPasskeyCommand = {
  passkeyId: string;
  passkeyName?: string;
  publicKeyCredential: any;
  sessionId: string;
};

type RegisterPasskeyCommand = {
  sessionId: string;
};

export async function registerPasskeyLink(
  command: RegisterPasskeyCommand,
): Promise<RegisterPasskeyResponse> {
  const { sessionId } = command;

  const sessionCookie = await getSessionCookieById({ sessionId });
  const session = await getSession({
    sessionId: sessionCookie.id,
    sessionToken: sessionCookie.token,
  });

  const host = (await headers()).get("host");

  if (!host) {
    throw new Error("Could not get domain");
  }

  const [hostname, port] = host.split(":");

  if (!hostname) {
    throw new Error("Could not get hostname");
  }

  const userId = session?.session?.factors?.user?.id;

  if (!userId) {
    throw new Error("Could not get session");
  }
  // TODO: add org context

  // use session token to add the passkey
  const registerLink = await createPasskeyRegistrationLink(
    userId,
    // sessionCookie.token,
  );

  if (!registerLink.code) {
    throw new Error("Missing code in response");
  }

  return registerPasskey(userId, registerLink.code, hostname);
}

export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) {
  // if no name is provided, try to generate one from the user agent
  let passkeyName = command.passkeyName;
  if (!!!passkeyName) {
    const headersList = await headers();
    const userAgentStructure = { headers: headersList };
    const { browser, device, os } = userAgent(userAgentStructure);

    passkeyName = `${device.vendor ?? ""} ${device.model ?? ""}${
      device.vendor || device.model ? ", " : ""
    }${os.name}${os.name ? ", " : ""}${browser.name}`;
  }

  const sessionCookie = await getSessionCookieById({
    sessionId: command.sessionId,
  });
  const session = await getSession({
    sessionId: sessionCookie.id,
    sessionToken: sessionCookie.token,
  });
  const userId = session?.session?.factors?.user?.id;

  if (!userId) {
    throw new Error("Could not get session");
  }

  return zitadelVerifyPasskeyRegistration(
    create(VerifyPasskeyRegistrationRequestSchema, {
      passkeyId: command.passkeyId,
      publicKeyCredential: command.publicKeyCredential,
      passkeyName,
      userId,
    }),
  );
}

type SendPasskeyCommand = {
  loginName?: string;
  sessionId?: string;
  organization?: string;
  checks?: Checks;
  authRequestId?: string;
  lifetime?: Duration;
};

export async function sendPasskey(command: SendPasskeyCommand) {
  let { loginName, sessionId, organization, checks, authRequestId } = command;
  const recentSession = sessionId
    ? await getSessionCookieById({ sessionId })
    : loginName
      ? await getSessionCookieByLoginName({ loginName, organization })
      : await getMostRecentSessionCookie();

  if (!recentSession) {
    return {
      error: "Could not find session",
    };
  }

  const host = (await headers()).get("host");

  if (!host) {
    return { error: "Could not get host" };
  }

  const loginSettings = await getLoginSettings(organization);

  const lifetime = checks?.webAuthN
    ? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey
    : checks?.otpEmail || checks?.otpSms
      ? loginSettings?.secondFactorCheckLifetime
      : undefined;

  const session = await setSessionAndUpdateCookie(
    recentSession,
    checks,
    undefined,
    authRequestId,
    lifetime,
  );

  if (!session || !session?.factors?.user?.id) {
    return { error: "Could not update session" };
  }

  const userResponse = await getUserByID(session?.factors?.user?.id);

  if (!userResponse.user) {
    return { error: "Could not find user" };
  }

  const humanUser =
    userResponse.user.type.case === "human"
      ? userResponse.user.type.value
      : undefined;

  const emailVerificationCheck = checkEmailVerification(
    session,
    humanUser,
    organization,
    authRequestId,
  );

  if (emailVerificationCheck?.redirect) {
    return emailVerificationCheck;
  }

  const url =
    authRequestId && session.id
      ? await getNextUrl(
          {
            sessionId: session.id,
            authRequestId: authRequestId,
            organization: organization,
          },
          loginSettings?.defaultRedirectUri,
        )
      : session?.factors?.user?.loginName
        ? await getNextUrl(
            {
              loginName: session.factors.user.loginName,
              organization: organization,
            },
            loginSettings?.defaultRedirectUri,
          )
        : null;

  return { redirect: url };
}
