import { NextRequest } from "next/server";
import { AffiliateCpaValidationMode, AffiliateOfferType, Prisma } from "@prisma/client";
import { z } from "zod";
import { prisma } from "@/server/db";
import { AppError } from "@/server/errors";
import { affiliateLinkCode, normalizeLandingPath, requireAffiliateAdmin } from "@/server/affiliation/service";
import { fail, ok, parseJson } from "@/server/http";

const linkSchema = z.object({
  id: z.string().uuid().optional(),
  accountId: z.string().uuid(),
  offerId: z.string().uuid().nullable().optional(),
  label: z.string().min(2).max(100),
  landingPath: z.string().max(180).optional().default("/"),
  status: z.enum(["active", "deactivated"]).default("active"),
  rewardType: z.nativeEnum(AffiliateOfferType).default(AffiliateOfferType.HYBRID),
  cpaAmount: z.coerce.number().min(0).max(100000).nullable().optional(),
  revenueShareBps: z.coerce.number().int().min(0).max(10000).nullable().optional(),
  minCpaDepositAmount: z.coerce.number().min(0).max(100000).default(0),
  cpaValidationMode: z.nativeEnum(AffiliateCpaValidationMode).default(AffiliateCpaValidationMode.FIRST_DEPOSIT),
  cpaWindowDays: z.coerce.number().int().min(0).max(365).nullable().optional(),
  allowedCountries: z.array(z.string().min(2).max(2)).max(80).default([]),
  terms: z.string().max(1800).nullable().optional()
});

export async function GET(request: NextRequest) {
  try {
    await requireAffiliateAdmin(request);
    const accountId = request.nextUrl.searchParams.get("accountId") ?? undefined;
    const links = await prisma.affiliateLink.findMany({
      where: accountId ? { accountId } : undefined,
      orderBy: { createdAt: "desc" },
      take: 200,
      include: { account: { include: { user: true } }, offer: true }
    });

    return ok({ links: links.map(serializeAdminLink) });
  } catch (error) {
    return fail(error);
  }
}

export async function POST(request: NextRequest) {
  try {
    await requireAffiliateAdmin(request);
    const input = await parseJson(request, linkSchema);
    validateLinkEconomics(input);
    const link = await prisma.affiliateLink.create({
      data: {
        ...linkData(input),
        accountId: input.accountId,
        code: await uniqueLinkCode(),
        offerId: input.offerId || undefined
      },
      include: { account: { include: { user: true } }, offer: true }
    });
    return ok({ link: serializeAdminLink(link) });
  } catch (error) {
    return fail(error);
  }
}

export async function PATCH(request: NextRequest) {
  try {
    await requireAffiliateAdmin(request);
    const input = await parseJson(request, linkSchema.extend({ id: z.string().uuid() }));
    validateLinkEconomics(input);
    const link = await prisma.affiliateLink.update({
      where: { id: input.id },
      data: {
        ...linkData(input),
        accountId: input.accountId,
        offerId: input.offerId || null
      },
      include: { account: { include: { user: true } }, offer: true }
    });
    return ok({ link: serializeAdminLink(link) });
  } catch (error) {
    return fail(error);
  }
}

export async function DELETE(request: NextRequest) {
  try {
    await requireAffiliateAdmin(request);
    const id = request.nextUrl.searchParams.get("id");
    if (!id) throw new AppError("Link id is required", 422, "AFFILIATE_LINK_ID_REQUIRED");
    const link = await prisma.affiliateLink.update({
      where: { id },
      data: { status: "deactivated" },
      include: { account: { include: { user: true } }, offer: true }
    });
    return ok({ link: serializeAdminLink(link) });
  } catch (error) {
    return fail(error);
  }
}

function validateLinkEconomics(input: z.infer<typeof linkSchema>) {
  if ((input.rewardType === AffiliateOfferType.CPA || input.rewardType === AffiliateOfferType.HYBRID) && !input.cpaAmount) {
    throw new AppError("CPA amount is required for this link", 422, "LINK_CPA_REQUIRED");
  }
  if ((input.rewardType === AffiliateOfferType.RS || input.rewardType === AffiliateOfferType.HYBRID) && !input.revenueShareBps) {
    throw new AppError("Revenue share is required for this link", 422, "LINK_RS_REQUIRED");
  }
}

function linkData(input: z.infer<typeof linkSchema>) {
  return {
    label: input.label,
    landingPath: normalizeLandingPath(input.landingPath),
    status: input.status,
    rewardType: input.rewardType,
    cpaAmount: input.rewardType === AffiliateOfferType.CPA || input.rewardType === AffiliateOfferType.HYBRID ? input.cpaAmount : null,
    revenueShareBps: input.rewardType === AffiliateOfferType.RS || input.rewardType === AffiliateOfferType.HYBRID ? input.revenueShareBps : null,
    minCpaDepositAmount: input.minCpaDepositAmount,
    cpaValidationMode: input.cpaValidationMode,
    cpaWindowDays: input.cpaWindowDays || null,
    allowedCountries: normalizeCountries(input.allowedCountries),
    terms: input.terms || null
  };
}

function normalizeCountries(countries: string[]) {
  return [...new Set(countries.map((country) => country.trim().toUpperCase()).filter(Boolean))];
}

async function uniqueLinkCode() {
  for (let index = 0; index < 12; index += 1) {
    const code = affiliateLinkCode();
    const existing = await prisma.affiliateLink.findUnique({ where: { code } });
    if (!existing) return code;
  }
  throw new AppError("Unable to allocate affiliate link", 503, "AFFILIATE_LINK_ALLOCATION_FAILED");
}

function serializeAdminLink(
  link: Prisma.AffiliateLinkGetPayload<{ include: { account: { include: { user: true } }; offer: true } }>
) {
  return {
    id: link.id,
    accountId: link.accountId,
    affiliate: link.account.displayName,
    affiliateEmail: link.account.user.email,
    code: link.code,
    label: link.label,
    landingPath: link.landingPath,
    status: link.status,
    rewardType: link.rewardType,
    cpaAmount: link.cpaAmount?.toString() ?? null,
    revenueShareBps: link.revenueShareBps,
    minCpaDepositAmount: link.minCpaDepositAmount.toString(),
    cpaValidationMode: link.cpaValidationMode,
    cpaWindowDays: link.cpaWindowDays,
    allowedCountries: link.allowedCountries,
    terms: link.terms,
    offer: link.offer ? { id: link.offer.id, code: link.offer.code, name: link.offer.name } : null,
    url: `/affiliation/r/${link.code}`,
    createdAt: link.createdAt.toISOString()
  };
}
