import { NextRequest, NextResponse } from "next/server";
import { Role } from "@prisma/client";
import { z } from "zod";
import { prisma } from "@/server/db";
import { AppError, ConflictError } from "@/server/errors";
import { fail, ok, parseJson } from "@/server/http";
import { AFFILIATION_COOKIE, attachAffiliateReferralInTx } from "@/server/affiliation/service";
import { createPendingWelcomeBonusInTx, getWelcomePromotionByCode, normalizePromotionCode } from "@/server/bonus/welcome";
import { clientIp, createSessionTokens } from "@/server/security/auth";
import { setAuthCookies } from "@/server/security/cookies";
import { enforceRateLimit } from "@/server/security/rate-limit";
import { hashPassword } from "@/server/security/password";
import { ensureWallet } from "@/server/wallet/ledger";

const registerSchema = z.object({
  username: z.string().min(3).max(24).regex(/^[a-zA-Z0-9_]+$/),
  email: z.string().email().transform((value) => value.toLowerCase()),
  password: z.string().min(12).max(128),
  firstName: z.string().min(1).max(80),
  lastName: z.string().min(1).max(80),
  dateOfBirth: z.coerce.date(),
  country: z.string().length(2),
  currency: z.string().min(3).max(8).default("EUR"),
  acceptedTerms: z.literal(true),
  welcomeBonusCode: z.string().max(40).nullable().optional()
});

export async function POST(request: NextRequest) {
  try {
    await enforceRateLimit(`register:${clientIp(request)}`, 5, 60);
    const input = await parseJson(request, registerSchema);

    const age = Date.now() - input.dateOfBirth.getTime();
    if (age < 18 * 365.2425 * 24 * 60 * 60 * 1000) {
      throw new AppError("You must be at least 18 years old", 422, "AGE_RESTRICTED");
    }

    const selectedBonusCode = normalizePromotionCode(input.welcomeBonusCode ?? "");
    const selectedPromotion = selectedBonusCode && selectedBonusCode !== "NONE" ? await getWelcomePromotionByCode(selectedBonusCode) : null;
    if (selectedBonusCode && selectedBonusCode !== "NONE" && !selectedPromotion) {
      throw new AppError("Code promotionnel indisponible", 422, "WELCOME_PROMO_NOT_FOUND");
    }

    const affiliateCode = request.cookies.get(AFFILIATION_COOKIE)?.value;
    const passwordHash = await hashPassword(input.password);
    const user = await prisma.$transaction(async (tx) => {
      const createdUser = await tx.user.create({
        data: {
          email: input.email,
          username: input.username,
          passwordHash,
          status: "ACTIVE",
          roles: [Role.USER],
          profile: {
            create: {
              firstName: input.firstName,
              lastName: input.lastName,
              dateOfBirth: input.dateOfBirth,
              country: input.country,
              currency: input.currency,
              language: "fr"
            }
          }
        },
        include: { profile: true }
      });

      if (selectedPromotion) {
        await createPendingWelcomeBonusInTx(tx, {
          userId: createdUser.id,
          promotion: selectedPromotion
        });
      }

      await attachAffiliateReferralInTx(tx, {
        userId: createdUser.id,
        country: input.country,
        code: affiliateCode
      });

      return createdUser;
    });

    await ensureWallet(user.id, input.currency);
    const tokens = await createSessionTokens({
      userId: user.id,
      roles: user.roles,
      ipAddress: clientIp(request),
      userAgent: request.headers.get("user-agent") ?? undefined
    });

    const response = ok({ user: sanitizeUser(user) }) as NextResponse;
    setAuthCookies(response, tokens);
    response.cookies.set(AFFILIATION_COOKIE, "", { path: "/", maxAge: 0 });
    return response;
  } catch (error) {
    if (isUniqueError(error)) {
      return fail(new ConflictError("Email or username is already registered", "USER_ALREADY_EXISTS"));
    }
    return fail(error);
  }
}

function sanitizeUser(user: { id: string; email: string; username: string; roles: Role[] }) {
  return { id: user.id, email: user.email, username: user.username, roles: user.roles };
}

function isUniqueError(error: unknown) {
  return typeof error === "object" && error !== null && "code" in error && error.code === "P2002";
}
