import crypto from "node:crypto";
import { NextRequest } from "next/server";
import { CryptoAsset } from "@prisma/client";
import { z } from "zod";
import { prisma } from "@/server/db";
import { env } from "@/server/env";
import { AppError, ForbiddenError } from "@/server/errors";
import { fail, ok } from "@/server/http";
import { requireCryptoAssetConfig } from "@/server/crypto/assets";
import { creditCryptoDeposit } from "@/server/crypto/payments";

export const dynamic = "force-dynamic";

const webhookSchema = z.object({
  asset: z.nativeEnum(CryptoAsset),
  txHash: z.string().min(8).max(160),
  toAddress: z.string().min(12).max(160),
  fromAddress: z.string().min(4).max(160).optional(),
  cryptoAmount: z.string().min(1).max(80),
  confirmations: z.coerce.number().int().min(0).max(100000).default(0)
});

export async function POST(request: NextRequest) {
  try {
    const rawBody = await request.text();
    verifySignature(rawBody, request.headers.get("x-crypto-signature"));
    const input = webhookSchema.parse(JSON.parse(rawBody));
    const assetConfig = requireCryptoAssetConfig(input.asset);
    const address = await prisma.cryptoDepositAddress.findUnique({
      where: { address: input.toAddress }
    });

    if (!address || address.network !== assetConfig.network) {
      throw new AppError("Unknown crypto deposit address", 404, "CRYPTO_ADDRESS_NOT_FOUND");
    }

    if (input.confirmations < assetConfig.requiredConfirmations) {
      return ok({
        credited: false,
        status: "CONFIRMING",
        confirmations: input.confirmations,
        requiredConfirmations: assetConfig.requiredConfirmations
      });
    }

    const deposit = await creditCryptoDeposit({
      userId: address.userId,
      asset: input.asset,
      cryptoAmount: input.cryptoAmount,
      txHash: input.txHash,
      fromAddress: input.fromAddress,
      confirmations: input.confirmations
    });

    return ok({
      credited: deposit.status === "CREDITED",
      depositId: deposit.id,
      eurAmount: deposit.eurAmount?.toString() ?? null,
      status: deposit.status
    });
  } catch (error) {
    return fail(error);
  }
}

function verifySignature(rawBody: string, signature: string | null) {
  if (!signature) {
    throw new ForbiddenError("Missing crypto webhook signature");
  }

  const expected = crypto.createHmac("sha256", env().CRYPTO_WEBHOOK_SECRET).update(rawBody).digest("hex");
  const received = signature.replace(/^sha256=/, "");
  const expectedBuffer = Buffer.from(expected, "hex");
  const receivedBuffer = Buffer.from(received, "hex");

  if (expectedBuffer.length !== receivedBuffer.length || !crypto.timingSafeEqual(expectedBuffer, receivedBuffer)) {
    throw new ForbiddenError("Invalid crypto webhook signature");
  }
}
