import { Prisma } from "@prisma/client";
import { NextResponse } from "next/server";

import { serializeStockAdjustment } from "@/features/stock-adjustments/lib/serialize-stock-adjustment";
import { generateStockAdjustmentNo } from "@/features/stock-adjustments/lib/generate-stock-adjustment-no";
import { stockAdjustmentSchema } from "@/features/stock-adjustments/schemas/stock-adjustment.schema";
import { createAuditTrailSafe } from "@/lib/audit-trail";
import { requireApiAccess } from "@/lib/authorization";
import { prisma } from "@/lib/prisma";

function parseBigInt(value: string) {
  try {
    return BigInt(value);
  } catch {
    return null;
  }
}

const stockAdjustmentInclude = {
  lot: {
    include: {
      grade: { select: { name: true } },
      unit: { select: { code: true } }
    }
  },
  adjustmentReason: true,
  createdBy: {
    include: {
      role: { select: { code: true } }
    }
  }
} as const;

export async function GET(request: Request) {
  const auth = requireApiAccess(request);
  if (!auth.ok) return auth.response;

  const data = await prisma.stockAdjustment.findMany({
    include: stockAdjustmentInclude,
    orderBy: [{ adjustmentDate: "desc" }, { createdAt: "desc" }]
  });

  return NextResponse.json({
    data: data.map(serializeStockAdjustment)
  });
}

export async function POST(request: Request) {
  const auth = requireApiAccess(request);
  if (!auth.ok) return auth.response;

  const parsed = stockAdjustmentSchema.safeParse(await request.json());
  if (!parsed.success) {
    return NextResponse.json(
      {
        message: "Validasi gagal",
        errors: parsed.error.flatten().fieldErrors
      },
      { status: 422 }
    );
  }

  const lotId = parseBigInt(parsed.data.lot_id);
  const reasonId = parseBigInt(parsed.data.adjustment_reason_id);
  const adjustmentDate = new Date(parsed.data.adjustment_date);

  if (lotId === null || reasonId === null || Number.isNaN(adjustmentDate.getTime())) {
    return NextResponse.json({ message: "Invalid reference id or date" }, { status: 400 });
  }

  const [lot, reason] = await Promise.all([
    prisma.inventoryLot.findUnique({ where: { id: lotId } }),
    prisma.adjustmentReason.findUnique({ where: { id: reasonId } })
  ]);

  if (!lot) {
    return NextResponse.json({ message: "Lot tidak ditemukan" }, { status: 404 });
  }
  if (!reason) {
    return NextResponse.json({ message: "Adjustment reason tidak ditemukan" }, { status: 404 });
  }

  const before = lot.availableQty.toNumber();
  const after = Number((before + parsed.data.qty_change).toFixed(3));

  if (after < 0) {
    return NextResponse.json(
      { message: `Qty adjustment membuat stok negatif. Available saat ini ${before}.` },
      { status: 422 }
    );
  }

  const adjustmentNo = await generateStockAdjustmentNo(adjustmentDate);
  const absChange = Math.abs(parsed.data.qty_change);

  const created = await prisma.$transaction(async (tx) => {
    const adjustment = await tx.stockAdjustment.create({
      data: {
        adjustmentNo,
        lotId,
        adjustmentReasonId: reasonId,
        adjustmentDate,
        qtyChange: new Prisma.Decimal(parsed.data.qty_change),
        availableQtyBefore: lot.availableQty,
        availableQtyAfter: new Prisma.Decimal(after),
        notes: parsed.data.notes ?? null,
        createdById: BigInt(auth.user.id)
      }
    });

    const lotUpdate: {
      availableQty: Prisma.Decimal;
      status: string;
      shrinkageQty?: Prisma.Decimal;
      damagedQty?: Prisma.Decimal;
    } = {
      availableQty: new Prisma.Decimal(after),
      status: after <= 0 ? "DEPLETED" : "ACTIVE"
    };

    if (parsed.data.qty_change < 0 && reason.category === "SHRINKAGE") {
      lotUpdate.shrinkageQty = new Prisma.Decimal(
        Number((lot.shrinkageQty.toNumber() + absChange).toFixed(3))
      );
    }

    if (parsed.data.qty_change < 0 && reason.category === "DAMAGE") {
      lotUpdate.damagedQty = new Prisma.Decimal(
        Number((lot.damagedQty.toNumber() + absChange).toFixed(3))
      );
    }

    await tx.inventoryLot.update({
      where: { id: lotId },
      data: lotUpdate
    });

    return tx.stockAdjustment.findUniqueOrThrow({
      where: { id: adjustment.id },
      include: stockAdjustmentInclude
    });
  });

  await createAuditTrailSafe({
    userId: auth.user.id,
    action: "STOCK_ADJUSTMENT_CREATED",
    entityType: "STOCK_ADJUSTMENT",
    entityId: created.id,
    method: request.method,
    pathname: new URL(request.url).pathname,
    statusCode: 201,
    summary: `Stock adjustment ${created.adjustmentNo} dibuat untuk ${created.lot.lotCode}`,
    metadata: {
      adjustment_no: created.adjustmentNo,
      lot_code: created.lot.lotCode,
      qty_change: created.qtyChange.toNumber()
    }
  });

  return NextResponse.json(
    {
      data: serializeStockAdjustment(created)
    },
    { status: 201 }
  );
}
