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

import { completeWashingSchema } from "@/features/washing/schemas/washing.schema";
import { buildAllocationShares, getEffectiveLotAllocations, roundAmount, roundQty } from "@/features/purchase-realization/lib/lot-allocation";
import { recalculatePurchaseRealizationSummary } from "@/features/purchase-realization/lib/recalculate-purchase-realization-summary";
import { serializeWashing } from "@/features/washing/lib/serialize-washing";
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 washingInclude = {
  lot: {
    include: {
      purchaseAllocations: true,
      purchase: {
        select: {
          id: true,
          agentId: true,
          profitShareSchemeId: true,
          profitShareScheme: {
            select: {
              shareAgent: true
            }
          },
          agent: {
            select: {
              name: true
            }
          }
        }
      },
      grade: { select: { name: true } },
      unit: { select: { code: true } },
      warehouse: { select: { name: true } },
      warehouseLocation: { select: { name: true } }
    }
  },
  washingPlace: {
    select: {
      id: true,
      name: true
    }
  }
} as const;

type WashingTx = Prisma.TransactionClient & {
  purchaseRealizationEntry: typeof prisma.purchaseRealizationEntry;
  purchaseRealizationSummary: typeof prisma.purchaseRealizationSummary;
  lotPurchaseAllocation: typeof prisma.lotPurchaseAllocation;
};

type RouteContext = {
  params: Promise<{
    id: string;
  }>;
};

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

  const washingId = parseBigInt((await context.params).id);
  if (washingId === null) {
    return NextResponse.json({ message: "Invalid id" }, { status: 400 });
  }

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

  const gradeId =
    parsed.data.grade_id && parsed.data.grade_id.trim() !== ""
      ? parseBigInt(parsed.data.grade_id)
      : null;
  const warehouseId = parseBigInt(parsed.data.warehouse_id);
  const warehouseLocationId =
    parsed.data.warehouse_location_id && parsed.data.warehouse_location_id.trim() !== ""
      ? parseBigInt(parsed.data.warehouse_location_id)
      : null;

  if (
    warehouseId === null ||
    (parsed.data.grade_id && gradeId === null) ||
    (parsed.data.warehouse_location_id && warehouseLocationId === null)
  ) {
    return NextResponse.json({ message: "Referensi grade atau gudang tidak valid" }, { status: 400 });
  }

  const washing = await prisma.lotWashing.findUnique({
    where: { id: washingId },
    include: {
      lot: {
        include: {
          purchase: {
            select: {
              id: true,
              agentId: true,
              profitShareSchemeId: true,
              profitShareScheme: {
                select: {
                  shareAgent: true
                }
              }
            }
          },
          purchaseAllocations: true,
          grade: true,
          warehouse: true,
          warehouseLocation: true
        }
      }
    }
  });
  if (!washing) {
    return NextResponse.json({ message: "Data washing tidak ditemukan" }, { status: 404 });
  }
  if (washing.status === "DONE") {
    return NextResponse.json({ message: "Washing ini sudah selesai" }, { status: 409 });
  }
  if (washing.lot.reservedQty.toNumber() > 0) {
    return NextResponse.json(
      { message: "Lot dengan qty reserved tidak bisa diselesaikan dari washing" },
      { status: 422 }
    );
  }

  const [grade, warehouse, warehouseLocation] = await Promise.all([
    gradeId ? prisma.grade.findUnique({ where: { id: gradeId } }) : Promise.resolve(null),
    prisma.warehouse.findUnique({ where: { id: warehouseId } }),
    warehouseLocationId
      ? prisma.warehouseLocation.findUnique({ where: { id: warehouseLocationId } })
      : Promise.resolve(null)
  ]);

  if (!warehouse) {
    return NextResponse.json({ message: "Gudang tujuan tidak ditemukan" }, { status: 404 });
  }
  if (warehouseLocationId && !warehouseLocation) {
    return NextResponse.json({ message: "Lokasi gudang tidak ditemukan" }, { status: 404 });
  }
  if (warehouseLocation && warehouseLocation.warehouseId !== warehouse.id) {
    return NextResponse.json(
      { message: "Lokasi gudang tidak sesuai dengan gudang tujuan" },
      { status: 422 }
    );
  }
  if (gradeId && !grade) {
    return NextResponse.json({ message: "Grade lot tidak ditemukan" }, { status: 404 });
  }

  const beforeQty = washing.lot.availableQty.toNumber();
  if (parsed.data.after_qty > beforeQty) {
    return NextResponse.json(
      { message: "Berat setelah dicuci tidak boleh lebih besar dari berat awal lot" },
      { status: 422 }
    );
  }

  const shrinkageQty = Number((beforeQty - parsed.data.after_qty).toFixed(3));

  const completed = await prisma.$transaction(async (rawTx) => {
    const tx = rawTx as WashingTx;
    await tx.inventoryLot.update({
      where: { id: washing.lotId },
      data: {
        originalQty: new Prisma.Decimal(parsed.data.after_qty),
        availableQty: new Prisma.Decimal(parsed.data.after_qty),
        shrinkageQty: new Prisma.Decimal(
          Number((washing.lot.shrinkageQty.toNumber() + shrinkageQty).toFixed(3))
        ),
        gradeId: gradeId ?? washing.lot.gradeId,
        warehouseId: warehouse.id,
        warehouseLocationId: warehouseLocation?.id ?? null,
        status: parsed.data.after_qty > 0 ? "ACTIVE" : "DEPLETED"
      }
    });

    await tx.lotWashing.update({
      where: { id: washing.id },
      data: {
        status: "DONE",
        completedAt: new Date(),
        completedById: BigInt(auth.user.id),
        afterQty: new Prisma.Decimal(parsed.data.after_qty),
        shrinkageQty: new Prisma.Decimal(shrinkageQty),
        afterGradeName: grade?.name ?? washing.lot.grade?.name ?? null,
        afterWarehouseName: warehouse.name,
        afterLocationName: warehouseLocation?.name ?? null
      }
    });

    const allocations = getEffectiveLotAllocations(washing.lot);
    const affectedPurchaseIds = new Set<bigint>();
    const sourceAvailableQty = washing.lot.availableQty.toNumber();
    const allocationShares = buildAllocationShares(allocations, sourceAvailableQty, beforeQty);

    for (const share of allocationShares) {
      affectedPurchaseIds.add(share.allocation.purchaseId);
      const washingCostAmount = roundAmount(washing.washingCost.toNumber() * share.ratio);
      if (washingCostAmount > 0) {
        await tx.purchaseRealizationEntry.create({
          data: {
            purchaseId: share.allocation.purchaseId,
            lotId: washing.lotId,
            allocationId: share.allocation.id ?? undefined,
            eventType: "WASHING_COST",
            referenceType: "LOT_WASHING",
            referenceId: washing.id,
            occurredAt: new Date(),
            qtyIn: new Prisma.Decimal(0),
            qtyOut: new Prisma.Decimal(0),
            qtyShrinkage: new Prisma.Decimal(0),
            amountCost: new Prisma.Decimal(washingCostAmount),
            amountRevenue: new Prisma.Decimal(0),
            amountExpense: new Prisma.Decimal(0),
            amountProfit: new Prisma.Decimal(0),
            agentSharePercentSnapshot: null,
            agentAmount: new Prisma.Decimal(0),
            notes: `Biaya washing ${washing.washingNo}`
          }
        });
      }

      const shrinkageAllocationQty = roundQty(shrinkageQty * share.ratio);
      if (shrinkageAllocationQty > 0) {
        await tx.purchaseRealizationEntry.create({
          data: {
            purchaseId: share.allocation.purchaseId,
            lotId: washing.lotId,
            allocationId: share.allocation.id ?? undefined,
            eventType: "WASHING_SHRINKAGE",
            referenceType: "LOT_WASHING",
            referenceId: washing.id,
            occurredAt: new Date(),
            qtyIn: new Prisma.Decimal(0),
            qtyOut: new Prisma.Decimal(0),
            qtyShrinkage: new Prisma.Decimal(shrinkageAllocationQty),
            amountCost: new Prisma.Decimal(0),
            amountRevenue: new Prisma.Decimal(0),
            amountExpense: new Prisma.Decimal(0),
            amountProfit: new Prisma.Decimal(0),
            agentSharePercentSnapshot: null,
            agentAmount: new Prisma.Decimal(0),
            notes: `Susut washing ${washing.washingNo}`
          }
        });
      }

      if (share.allocation.id) {
        await tx.lotPurchaseAllocation.update({
          where: { id: share.allocation.id },
          data: {
            qtyAllocated: new Prisma.Decimal(roundQty(parsed.data.after_qty * share.ratio)),
            costTotalAllocated: new Prisma.Decimal(
              roundAmount(parsed.data.after_qty * share.ratio * share.allocation.unitCostSnapshot.toNumber())
            )
          }
        });
      }
    }

    for (const purchaseId of affectedPurchaseIds) {
      const sourcePurchase = await tx.purchase.findUnique({
        where: { id: purchaseId },
        select: {
          profitShareScheme: {
            select: {
              shareAgent: true
            }
          }
        }
      });
      await recalculatePurchaseRealizationSummary(
        tx,
        purchaseId,
        sourcePurchase?.profitShareScheme?.shareAgent.toNumber() ?? null
      );
    }

    return tx.lotWashing.findUniqueOrThrow({
      where: { id: washing.id },
      include: washingInclude
    });
  });

  await createAuditTrailSafe({
    userId: auth.user.id,
    action: "WASHING_COMPLETED",
    entityType: "LOT_WASHING",
    entityId: completed.id,
    method: request.method,
    pathname: new URL(request.url).pathname,
    statusCode: 200,
    summary: `Washing ${completed.washingNo} selesai`,
    metadata: {
      washing_no: completed.washingNo,
      lot_code: completed.lot.lotCode,
      before_qty: beforeQty,
      after_qty: parsed.data.after_qty,
      shrinkage_qty: shrinkageQty
    }
  });

  return NextResponse.json({ data: serializeWashing(completed) });
}
