import crypto from "node:crypto";
import { z } from "zod/v4";
import type { PaymentStatus } from "@/app/generated/prisma/enums";

/**
 * Schema validasi callback Midtrans. Field minimum yang kita pakai +
 * passthrough untuk field lain (Midtrans bisa tambah field tanpa breaking).
 */
export const midtransWebhookSchema = z.object({
  order_id: z.string().min(1, "order_id wajib"),
  status_code: z.string().min(1, "status_code wajib"),
  gross_amount: z.string().min(1, "gross_amount wajib"),
  signature_key: z.string().min(1, "signature_key wajib"),
  transaction_status: z.string().min(1, "transaction_status wajib"),
  transaction_id: z.string().optional(),
  payment_type: z.string().optional(),
  fraud_status: z.string().nullable().optional(),
}).passthrough();

export type MidtransWebhookBody = z.infer<typeof midtransWebhookSchema>;

/**
 * Thin client untuk Midtrans Snap.
 *
 * Tidak pakai library `midtrans-client` — kita cukup `fetch` + `crypto.createHash`
 * untuk verifikasi signature. Lebih ringkas dan dependency-free.
 */

function isProduction(): boolean {
  return process.env.NEXT_PUBLIC_MIDTRANS_IS_PRODUCTION === "true";
}

export const MIDTRANS = {
  isProduction,
  /** Server-side base URL untuk Snap REST API. */
  snapApiBase: () =>
    isProduction()
      ? "https://app.midtrans.com/snap/v1"
      : "https://app.sandbox.midtrans.com/snap/v1",
  /** Public URL Snap.js untuk client. */
  snapJsUrl: () =>
    isProduction()
      ? "https://app.midtrans.com/snap/snap.js"
      : "https://app.sandbox.midtrans.com/snap/snap.js",
  /** Core API base — dipakai untuk GET /v2/{order_id}/status (rekonsiliasi). */
  coreApiBase: () =>
    isProduction()
      ? "https://api.midtrans.com/v2"
      : "https://api.sandbox.midtrans.com/v2",
};

function requireServerKey(): string {
  const key = process.env.MIDTRANS_SERVER_KEY;
  if (!key) {
    throw new Error(
      "MIDTRANS_SERVER_KEY belum di-set. Cek env.example."
    );
  }
  return key;
}

function basicAuthHeader(): string {
  // Midtrans pakai Basic auth dengan serverKey di username, password kosong.
  const token = Buffer.from(`${requireServerKey()}:`).toString("base64");
  return `Basic ${token}`;
}

interface SnapTransactionPayload {
  orderId: string;
  grossAmount: number;
  customer: {
    name: string;
    email: string;
  };
  itemName: string;
  /// Berapa detik sampai expire. Default Midtrans 24 jam, kita pakai itu kalau undefined.
  expirySeconds?: number;
  /// URL absolut untuk redirect user setelah selesai bayar (success / pending / error).
  /// Tanpa ini, Midtrans pakai default `example.com`.
  finishUrl?: string;
}

export interface SnapTransactionResult {
  token: string;
  redirectUrl: string;
}

/**
 * Buat transaksi Snap di Midtrans, dapatkan token untuk dipakai window.snap.pay().
 * https://docs.midtrans.com/reference/charge-transactions-1
 */
export async function createSnapTransaction(
  payload: SnapTransactionPayload
): Promise<SnapTransactionResult> {
  const body: Record<string, unknown> = {
    transaction_details: {
      order_id: payload.orderId,
      gross_amount: payload.grossAmount,
    },
    customer_details: {
      first_name: payload.customer.name,
      email: payload.customer.email,
    },
    item_details: [
      {
        id: payload.orderId,
        price: payload.grossAmount,
        quantity: 1,
        name: payload.itemName.slice(0, 50),
      },
    ],
  };

  if (payload.expirySeconds) {
    body.expiry = {
      unit: "second",
      duration: payload.expirySeconds,
    };
  }

  if (payload.finishUrl) {
    body.callbacks = { finish: payload.finishUrl };
  }

  const res = await fetch(`${MIDTRANS.snapApiBase()}/transactions`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
      Authorization: basicAuthHeader(),
    },
    body: JSON.stringify(body),
    cache: "no-store",
  });

  const json = (await res.json().catch(() => null)) as
    | { token?: string; redirect_url?: string; error_messages?: string[] }
    | null;

  if (!res.ok || !json?.token) {
    const reason =
      json?.error_messages?.join(", ") ?? `HTTP ${res.status}`;
    throw new Error(`Midtrans Snap gagal: ${reason}`);
  }

  return {
    token: json.token,
    redirectUrl: json.redirect_url ?? "",
  };
}

/**
 * Bentuk minimal response dari Midtrans Core API GET /v2/{order_id}/status.
 * Sub-set field yang kita pakai untuk rekonsiliasi (sama dengan field webhook).
 * https://docs.midtrans.com/reference/get-transaction-status
 */
export interface MidtransTransactionStatus {
  order_id: string;
  status_code: string;
  transaction_status: string;
  gross_amount: string;
  transaction_id?: string;
  payment_type?: string;
  fraud_status?: string | null;
}

/**
 * Fetch status transaksi langsung dari Midtrans untuk rekonsiliasi server-side.
 * Dipakai saat kita tidak bisa mengandalkan webhook (mis. dev di localhost,
 * atau webhook tertunda). Auth pakai server key — response sudah terpercaya
 * karena datang dari Midtrans atas request kita, jadi tidak perlu verifikasi
 * signature.
 *
 * Return null kalau Midtrans tidak menemukan order (404).
 */
export async function fetchMidtransTransactionStatus(
  orderId: string
): Promise<MidtransTransactionStatus | null> {
  const res = await fetch(
    `${MIDTRANS.coreApiBase()}/${encodeURIComponent(orderId)}/status`,
    {
      method: "GET",
      headers: {
        Accept: "application/json",
        Authorization: basicAuthHeader(),
      },
      cache: "no-store",
    }
  );

  if (res.status === 404) return null;

  const json = (await res.json().catch(() => null)) as
    | (Partial<MidtransTransactionStatus> & { status_message?: string })
    | null;

  if (!res.ok || !json?.order_id || !json.transaction_status) {
    const reason = json?.status_message ?? `HTTP ${res.status}`;
    throw new Error(`Midtrans status fetch gagal: ${reason}`);
  }

  return {
    order_id: json.order_id,
    status_code: json.status_code ?? String(res.status),
    transaction_status: json.transaction_status,
    gross_amount: json.gross_amount ?? "0",
    transaction_id: json.transaction_id,
    payment_type: json.payment_type,
    fraud_status: json.fraud_status ?? null,
  };
}

/**
 * Verifikasi signature webhook Midtrans.
 * Formula: SHA512(order_id + status_code + gross_amount + serverKey).
 * https://docs.midtrans.com/reference/notification-webhooks-1
 */
export function verifyMidtransSignature(
  orderId: string,
  statusCode: string,
  grossAmount: string,
  signatureKey: string
): boolean {
  const expected = crypto
    .createHash("sha512")
    .update(`${orderId}${statusCode}${grossAmount}${requireServerKey()}`)
    .digest("hex");

  // Konstanta-time compare untuk hindari timing attack.
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(signatureKey, "hex");
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

/**
 * Map kombinasi `transaction_status` + `fraud_status` Midtrans ke `PaymentStatus` internal.
 *
 * | Midtrans            | fraud_status | PaymentStatus |
 * |---------------------|--------------|---------------|
 * | capture             | accept       | PAID          |
 * | capture             | challenge    | AWAITING      |
 * | settlement          | —            | PAID          |
 * | pending             | —            | AWAITING      |
 * | deny                | —            | FAILED        |
 * | expire              | —            | EXPIRED       |
 * | cancel              | —            | CANCELLED     |
 * | refund / partial    | —            | REFUNDED      |
 */
export function mapMidtransStatus(
  transactionStatus: string,
  fraudStatus?: string | null
): PaymentStatus {
  switch (transactionStatus) {
    case "capture":
      // Khusus kartu kredit. fraud_status menentukan apakah sudah aman atau perlu review.
      if (fraudStatus === "challenge") return "AWAITING";
      if (fraudStatus === "accept") return "PAID";
      return "AWAITING";
    case "settlement":
      return "PAID";
    case "pending":
      return "AWAITING";
    case "deny":
      return "FAILED";
    case "expire":
      return "EXPIRED";
    case "cancel":
      return "CANCELLED";
    case "refund":
    case "partial_refund":
      return "REFUNDED";
    default:
      return "AWAITING";
  }
}
