import { json, type LoaderFunctionArgs } from "@remix-run/server-runtime";
import { type WhoAmIResponse } from "@trigger.dev/core/v3";
import { prisma } from "~/db.server";
import { env } from "~/env.server";
import { v3ProjectPath } from "~/utils/pathBuilder";
import { authenticateRequest } from "~/services/apiAuth.server";

export async function loader({ request }: LoaderFunctionArgs) {
  const authenticationResult = await authenticateRequest(request, {
    personalAccessToken: true,
    organizationAccessToken: true,
    apiKey: false,
  });

  if (!authenticationResult) {
    return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
  }

  const url = new URL(request.url);
  const projectRef = url.searchParams.get("projectRef") ?? undefined;

  switch (authenticationResult.type) {
    case "personalAccessToken": {
      const result = await getIdentityFromPAT(authenticationResult.result.userId, projectRef);
      if (!result.success) {
        if (result.error === "user_not_found") {
          return json({ error: "User not found" }, { status: 404 });
        }

        return json({ error: result.error }, { status: 401 });
      }
      return json(result.result);
    }
    case "organizationAccessToken": {
      const result = await getIdentityFromOAT(
        authenticationResult.result.organizationId,
        projectRef
      );
      return json(result.result);
    }
    default: {
      authenticationResult satisfies never;
      return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
    }
  }
}

async function getIdentityFromPAT(
  userId: string,
  projectRef: string | undefined
): Promise<
  { success: true; result: WhoAmIResponse } | { success: false; error: "user_not_found" }
> {
  const user = await prisma.user.findFirst({
    select: {
      email: true,
    },
    where: {
      id: userId,
    },
  });

  if (!user) {
    return { success: false, error: "user_not_found" };
  }

  const userDetails = {
    userId,
    email: user.email,
    dashboardUrl: env.APP_ORIGIN,
  } satisfies WhoAmIResponse;

  if (!projectRef) {
    return {
      success: true,
      result: userDetails,
    };
  }

  const orgs = await prisma.organization.findMany({
    select: {
      id: true,
    },
    where: {
      members: {
        some: {
          userId,
        },
      },
    },
  });

  if (orgs.length === 0) {
    return {
      success: true,
      result: userDetails,
    };
  }

  const project = await prisma.project.findFirst({
    select: {
      externalRef: true,
      name: true,
      slug: true,
      organization: {
        select: {
          slug: true,
          title: true,
        },
      },
    },
    where: {
      externalRef: projectRef,
      organizationId: {
        in: orgs.map((org) => org.id),
      },
    },
  });

  if (!project) {
    return {
      success: true,
      result: userDetails,
    };
  }

  const projectPath = v3ProjectPath({ slug: project.organization.slug }, { slug: project.slug });

  return {
    success: true,
    result: {
      ...userDetails,
      project: {
        url: new URL(projectPath, env.APP_ORIGIN).href,
        name: project.name,
        orgTitle: project.organization.title,
      },
    },
  };
}

async function getIdentityFromOAT(
  organizationId: string,
  projectRef: string | undefined
): Promise<{ success: true; result: WhoAmIResponse }> {
  // Organization auth tokens are currently only used internally for the build server.
  // We will eventually expose them in the application as well, as they are useful beyond the build server.
  // At that point we will need a v3 whoami endpoint that properly handles org auth tokens.
  // For now, we just return a dummy user id and email and keep using the existing v2 whoami endpoint.
  const orgDetails = {
    userId: `org_${organizationId}`,
    email: "not_applicable@trigger.dev",
    dashboardUrl: env.APP_ORIGIN,
  } satisfies WhoAmIResponse;

  if (!projectRef) {
    return {
      success: true,
      result: orgDetails,
    };
  }

  const project = await prisma.project.findFirst({
    select: {
      externalRef: true,
      name: true,
      slug: true,
      organization: {
        select: {
          slug: true,
          title: true,
        },
      },
    },
    where: {
      externalRef: projectRef,
      organizationId,
    },
  });

  if (!project) {
    return {
      success: true,
      result: orgDetails,
    };
  }

  const projectPath = v3ProjectPath(project.organization, project);
  return {
    success: true,
    result: {
      ...orgDetails,
      project: {
        url: new URL(projectPath, env.APP_ORIGIN).href,
        name: project.name,
        orgTitle: project.organization.title,
      },
    },
  };
}
