import crypto from "node:crypto";
import { NextRequest, NextResponse } from "next/server";
import { Prisma, Role } from "@prisma/client";
import { z } from "zod";
import { prisma } from "@/server/db";
import { ConflictError } from "@/server/errors";
import { fail, ok, parseJson } from "@/server/http";
import { affiliatePublicId } from "@/server/affiliation/service";
import { clientIp, createSessionTokens } from "@/server/security/auth";
import { setAuthCookies } from "@/server/security/cookies";
import { hashPassword } from "@/server/security/password";
import { enforceRateLimit } from "@/server/security/rate-limit";

const registerSchema = z.object({
  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),
  displayName: z.string().min(2).max(80),
  companyName: z.string().max(120).optional().nullable(),
  website: z.string().url().max(240).optional().nullable(),
  trafficSources: z.array(z.string().min(2).max(80)).max(8).default([]),
  payoutMethod: z.string().max(80).optional().nullable(),
  acceptedTerms: z.literal(true)
});

export async function POST(request: NextRequest) {
  try {
    await enforceRateLimit(`aff-register:${clientIp(request)}`, 5, 60);
    const input = await parseJson(request, registerSchema);
    const passwordHash = await hashPassword(input.password);
    const username = await uniqueUsername(input.email);

    const user = await prisma.$transaction(async (tx) => {
      const activeOffer = await tx.affiliateOffer.findFirst({
        where: { status: "ACTIVE" },
        orderBy: { createdAt: "desc" }
      });

      const createdUser = await tx.user.create({
        data: {
          email: input.email,
          username,
          passwordHash,
          status: "ACTIVE",
          roles: [Role.USER],
          profile: {
            create: {
              firstName: input.firstName,
              lastName: input.lastName,
              dateOfBirth: input.dateOfBirth,
              country: input.country,
              currency: "EUR",
              language: "fr"
            }
          }
        }
      });

      const account = await tx.affiliateAccount.create({
        data: {
          userId: createdUser.id,
          publicId: await uniqueAffiliatePublicId(tx),
          displayName: input.displayName,
          companyName: input.companyName || undefined,
          website: input.website || undefined,
          country: input.country,
          trafficSources: input.trafficSources,
          payoutMethod: input.payoutMethod || undefined,
          defaultOfferId: activeOffer?.id
        }
      });

      return createdUser;
    });

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

    const response = ok({ user: { id: user.id, email: user.email, username: user.username } }) as NextResponse;
    setAuthCookies(response, tokens);
    return response;
  } catch (error) {
    if (isUniqueError(error)) {
      return fail(new ConflictError("Email déjà utilisé", "AFFILIATE_ALREADY_EXISTS"));
    }
    return fail(error);
  }
}

async function uniqueUsername(email: string) {
  const base = email.split("@")[0]?.replace(/[^a-zA-Z0-9_]/g, "_").slice(0, 18) || "affiliate";
  for (let index = 0; index < 20; index += 1) {
    const username = index === 0 ? base : `${base}_${index}`;
    const existing = await prisma.user.findUnique({ where: { username } });
    if (!existing) return username;
  }
  return `affiliate_${cryptoRandom()}`;
}

async function uniqueAffiliatePublicId(tx: Prisma.TransactionClient) {
  for (let index = 0; index < 10; index += 1) {
    const publicId = affiliatePublicId();
    const existing = await tx.affiliateAccount.findUnique({ where: { publicId } });
    if (!existing) return publicId;
  }
  return `AFF-${cryptoRandom().toUpperCase()}`;
}

function cryptoRandom() {
  return crypto.randomBytes(5).toString("hex");
}

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