import process from "node:process";
import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils";
import { getLocaleFromRequest } from "@calcom/features/auth/lib/getLocaleFromRequest";
import { sendEmailVerification } from "@calcom/features/auth/lib/verifyEmail";
import { SIGNUP_ERROR_CODES } from "@calcom/features/auth/signup/constants";
import { createOrUpdateMemberships } from "@calcom/features/auth/signup/utils/createOrUpdateMemberships";
import { joinAnyChildTeamOnOrgInvite } from "@calcom/features/auth/signup/utils/organization";
import { prefillAvatar } from "@calcom/features/auth/signup/utils/prefillAvatar";
import {
  findTokenByToken,
  throwIfTokenExpired,
  validateAndGetCorrectedUsernameForTeam,
} from "@calcom/features/auth/signup/utils/token";
import { validateAndGetCorrectedUsernameAndEmail } from "@calcom/features/auth/signup/utils/validateUsername";
import { getFeatureRepository } from "@calcom/features/di/containers/FeatureRepository";
import { getUserRepository } from "@calcom/features/di/containers/UserRepository";
import { GlobalWatchlistRepository } from "@calcom/features/watchlist/lib/repository/GlobalWatchlistRepository";
import { sentrySpan } from "@calcom/features/watchlist/lib/telemetry";
import { normalizeEmail } from "@calcom/features/watchlist/lib/utils/normalization";
import { checkIfEmailIsBlockedInWatchlistController } from "@calcom/features/watchlist/operations/check-if-email-in-watchlist.controller";
import { hashPassword } from "@calcom/lib/auth/hashPassword";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
import { isPrismaError } from "@calcom/lib/server/getServerErrorFromUnknown";
import type { CustomNextApiHandler } from "@calcom/lib/server/username";
import { usernameHandler } from "@calcom/lib/server/username";
import { getTrackingFromCookies } from "@calcom/lib/tracking";
import { prisma } from "@calcom/prisma";
import {
  CreationSource,
  IdentityProvider,
  WatchlistAction,
  WatchlistSource,
  WatchlistType,
} from "@calcom/prisma/enums";
import { signupSchema } from "@calcom/prisma/zod-utils";
import { buildLegacyRequest } from "@calcom/web/lib/buildLegacyCtx";
import { cookies, headers } from "next/headers";
import { NextResponse } from "next/server";

const log = logger.getSubLogger({ prefix: ["signupCalcomHandler"] });

const billingService = {
  async createCustomer(_args: Record<string, unknown>): Promise<{ stripeCustomerId: string }> {
    return { stripeCustomerId: "" };
  },
  async createSubscriptionCheckout(
    _args: Record<string, unknown>
  ): Promise<{ sessionId: string }> {
    return { sessionId: "" };
  },
};

const handler: CustomNextApiHandler = async (body, usernameStatus, query) => {
  const {
    email: _email,
    password,
    token,
  } = signupSchema
    .pick({
      email: true,
      password: true,
      token: true,
    })
    .parse(body);

  const userRepository = getUserRepository();

  const shouldLockByDefault = await checkIfEmailIsBlockedInWatchlistController({
    email: _email,
    organizationId: null,
    span: sentrySpan,
  });

  log.debug("handler", { email: _email });

  let username: string | null = usernameStatus.requestedUserName;
  let checkoutSessionId: string | null = null;

  // Check for premium username
  if (usernameStatus.statusCode === 418) {
    return NextResponse.json(usernameStatus.json, { status: 418 });
  }

  // Validate the user
  if (!username) {
    throw new HttpError({
      statusCode: 422,
      message: "Invalid username",
    });
  }

  const email = _email.toLowerCase();

  let foundToken: { id: number; teamId: number | null; expires: Date } | null = null;
  if (token) {
    foundToken = await findTokenByToken({ token });
    throwIfTokenExpired(foundToken?.expires);
    username = await validateAndGetCorrectedUsernameForTeam({
      username,
      email,
      teamId: foundToken?.teamId ?? null,
      isSignup: true,
    });

    if (foundToken?.teamId) {
      const existingUser = await userRepository.findByEmailWithInvitedTo({email})

      if (existingUser && existingUser.invitedTo !== foundToken.teamId) {
        return NextResponse.json({ message: SIGNUP_ERROR_CODES.USER_ALREADY_EXISTS }, { status: 409 });
      }
    }
  } else {
    const usernameAndEmailValidation = await validateAndGetCorrectedUsernameAndEmail({
      username,
      email,
      isSignup: true,
    });
    if (!usernameAndEmailValidation.isValid) {
      throw new HttpError({
        statusCode: 409,
        message: "Username or email is already taken",
      });
    }

    if (!usernameAndEmailValidation.username) {
      throw new HttpError({
        statusCode: 422,
        message: "Invalid username",
      });
    }

    username = usernameAndEmailValidation.username;
  }

  // Create the customer in Stripe with ad tracking metadata
  const cookieStore = await cookies();
  const cookiesObj = Object.fromEntries(cookieStore.getAll().map((c) => [c.name, c.value]));
  const tracking = getTrackingFromCookies(cookiesObj, query);

  const customer = await billingService.createCustomer({
    email,
    metadata: {
      email /* Stripe customer email can be changed, so we add this to keep track of which email was used to signup */,
      username,
      ...(tracking.googleAds?.gclid && {
        gclid: tracking.googleAds.gclid,
        campaignId: tracking.googleAds.campaignId,
      }),
      ...(tracking.linkedInAds?.liFatId && {
        liFatId: tracking.linkedInAds.liFatId,
        linkedInCampaignId: tracking.linkedInAds.campaignId,
      }),
    },
  });

  const returnUrl = `${WEBAPP_URL}/api/integrations/stripepayment/paymentCallback?checkoutSessionId={CHECKOUT_SESSION_ID}&callbackUrl=/auth/verify?sessionId={CHECKOUT_SESSION_ID}`;

  // Pro username, must be purchased
  if (usernameStatus.statusCode === 402) {
    const checkoutSession = await billingService.createSubscriptionCheckout({
      mode: "subscription",
      customerId: customer.stripeCustomerId,
      successUrl: returnUrl,
      cancelUrl: returnUrl,
      priceId: getPremiumMonthlyPlanPriceId(),
      quantity: 1,
      allowPromotionCodes: true,
    });

    /** We create a username-less user until he pays */
    checkoutSessionId = checkoutSession.sessionId;
    username = null;
  }

  // Hash the password
  const hashedPassword = await hashPassword(password);

  if (foundToken?.teamId) {
    const team = await prisma.team.findUnique({
      where: {
        id: foundToken.teamId,
      },
      include: {
        parent: {
          select: {
            id: true,
            slug: true,
            organizationSettings: true,
          },
        },
        organizationSettings: true,
      },
    });
    if (team) {
      const organizationId = team.isOrganization ? team.id : (team.parent?.id ?? null);

      if (username) {
        const existingUserByUsername = await userRepository.findByUsernameAndOrganizationId({
          username,
          organizationId,
          excludeEmail: email
        })
        if (existingUserByUsername) {
          return NextResponse.json({ message: SIGNUP_ERROR_CODES.USER_ALREADY_EXISTS }, { status: 409 });
        }
      }

      let user: { id: number };
      try {
        user = await userRepository.upsertForSignup({
          email,
          username,
          hashedPassword,
          organizationId,
          emailVerified: new Date(),
          identityProvider: IdentityProvider.CAL
        })
      } catch (error) {
        if (isPrismaError(error) && error.code === "P2002") {
          const target = String(error.meta?.target ?? "");
          if (target.includes("email") || target.includes("username")) {
            return NextResponse.json({ message: SIGNUP_ERROR_CODES.USER_ALREADY_EXISTS }, { status: 409 });
          }
        }
        throw error;
      }

      await createOrUpdateMemberships({
        user,
        team,
      });

      // Accept any child team invites for orgs.
      if (team.parent) {
        await joinAnyChildTeamOnOrgInvite({
          userId: user.id,
          org: team.parent,
        });
      }
    }

    // Cleanup token after use
    await prisma.verificationToken.delete({
      where: {
        id: foundToken.id,
      },
    });
  } else {
    // Create the user
    try {
      await userRepository.create({
        username,
        email,
        hashedPassword,
        organizationId: null,
        creationSource: CreationSource.WEBAPP,
        locked: shouldLockByDefault,
        identityProvider: IdentityProvider.CAL,
        metadata: {
          stripeCustomerId: customer.stripeCustomerId,
          checkoutSessionId,
        }
      })
    } catch (error) {
      // Fallback for race conditions where user was created between our check and create
      if (isPrismaError(error) && error.code === "P2002") {
        const target = String(error.meta?.target ?? "");
        if (target.includes("email") || target.includes("username")) {
          return NextResponse.json({ message: SIGNUP_ERROR_CODES.USER_ALREADY_EXISTS }, { status: 409 });
        }
      }
      throw error;
    }
    if (process.env.AVATARAPI_USERNAME && process.env.AVATARAPI_PASSWORD) {
      await prefillAvatar({ email });
    }
  }

  const featureRepository = getFeatureRepository();
  const signupWatchlistReviewEnabled =
    await featureRepository.checkIfFeatureIsEnabledGlobally("signup-watchlist-review");

  if (signupWatchlistReviewEnabled && !token) {
    const globalWatchlistRepo = new GlobalWatchlistRepository(prisma);
    const normalizedEmail = normalizeEmail(email);
    const existing = await globalWatchlistRepo.findBlockedEmail(normalizedEmail);

    if (!existing) {
      await globalWatchlistRepo.createEntry({
        type: WatchlistType.EMAIL,
        value: normalizedEmail,
        action: WatchlistAction.BLOCK,
        source: WatchlistSource.SIGNUP,
        description: "Auto-added during signup review",
      });
    }

    await userRepository.lockByEmail({ email });

    return NextResponse.json(
      { message: "Created user", stripeCustomerId: customer.stripeCustomerId, accountUnderReview: true },
      { status: 201 }
    );
  }

  if (!checkoutSessionId && !token) {
    sendEmailVerification({
      email,
      language: await getLocaleFromRequest(buildLegacyRequest(await headers(), await cookies())),
      username: username || "",
    });
  }

  if (checkoutSessionId) {
    console.log("Created user but missing payment", checkoutSessionId);
    return NextResponse.json(
      { message: "Created user but missing payment", checkoutSessionId },
      { status: 402 }
    );
  }

  return NextResponse.json(
    { message: "Created user", stripeCustomerId: customer.stripeCustomerId },
    { status: 201 }
  );
};

export default usernameHandler(handler);
