import crypto from "node:crypto";
import { LedgerEntryType, PaymentType, TransactionStatus } from "@prisma/client";
import { z } from "zod";
import { prisma } from "@/server/db";
import { env } from "@/server/env";
import { AppError, ConflictError } from "@/server/errors";
import { creditAvailable, decimal } from "@/server/wallet/ledger";
import { writeAuditLog } from "@/server/audit";

export const paymentWebhookSchema = z.object({
  idempotencyKey: z.string().min(8),
  transactionId: z.string().uuid().optional(),
  providerRef: z.string().optional(),
  type: z.nativeEnum(PaymentType),
  status: z.nativeEnum(TransactionStatus),
  amount: z.coerce.string(),
  currency: z.string().min(3).max(8)
});

export function verifyHmacSignature(rawBody: string, signature: string | null, secret: string) {
  if (!signature) {
    return false;
  }

  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const actual = signature.replace(/^sha256=/, "");

  if (expected.length !== actual.length) {
    return false;
  }

  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(actual));
}

export async function handlePaymentWebhook(rawBody: string, signature: string | null) {
  const config = env();
  const signatureValid = verifyHmacSignature(rawBody, signature, config.PAYMENT_WEBHOOK_SECRET);
  const parsed = paymentWebhookSchema.parse(JSON.parse(rawBody));

  if (!signatureValid) {
    await prisma.webhookEvent.create({
      data: {
        provider: config.PAYMENT_PROVIDER,
        topic: "payment",
        idempotencyKey: `invalid:${parsed.idempotencyKey}`,
        signatureValid,
        payload: parsed
      }
    });
    throw new AppError("Invalid payment webhook signature", 401, "INVALID_SIGNATURE");
  }

  const existingWebhook = await prisma.webhookEvent.findUnique({ where: { idempotencyKey: parsed.idempotencyKey } });
  if (existingWebhook?.processedAt) {
    return { duplicate: true };
  }

  const transaction = parsed.transactionId
    ? await prisma.paymentTransaction.findUnique({ where: { id: parsed.transactionId } })
    : await prisma.paymentTransaction.findFirst({
        where: {
          OR: [{ providerRef: parsed.providerRef }, { idempotencyKey: parsed.idempotencyKey }]
        }
      });

  if (!transaction) {
    throw new ConflictError("Payment transaction not found", "PAYMENT_TRANSACTION_NOT_FOUND");
  }

  if (!decimal(transaction.amount).equals(decimal(parsed.amount)) || transaction.currency !== parsed.currency) {
    throw new ConflictError("Webhook amount or currency mismatch", "PAYMENT_AMOUNT_MISMATCH");
  }

  await prisma.webhookEvent.upsert({
    where: { idempotencyKey: parsed.idempotencyKey },
    create: {
      provider: config.PAYMENT_PROVIDER,
      topic: "payment",
      idempotencyKey: parsed.idempotencyKey,
      signatureValid,
      payload: parsed
    },
    update: {
      signatureValid,
      payload: parsed
    }
  });

  if (parsed.status === TransactionStatus.APPROVED && parsed.type === PaymentType.DEPOSIT) {
    await creditAvailable({
      userId: transaction.userId,
      currency: transaction.currency,
      amount: transaction.amount,
      type: LedgerEntryType.DEPOSIT,
      referenceType: "payment_transaction",
      referenceId: transaction.id,
      idempotencyKey: `payment:${parsed.idempotencyKey}`,
      metadata: { providerRef: parsed.providerRef }
    });
  }

  await prisma.paymentTransaction.update({
    where: { id: transaction.id },
    data: {
      status: parsed.status,
      providerRef: parsed.providerRef ?? transaction.providerRef
    }
  });

  await prisma.webhookEvent.update({
    where: { idempotencyKey: parsed.idempotencyKey },
    data: { processedAt: new Date() }
  });

  await writeAuditLog({
    action: "payment.webhook.process",
    entityType: "PaymentTransaction",
    entityId: transaction.id,
    after: parsed
  });

  return { duplicate: false, transactionId: transaction.id };
}
