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

import { generateTransformationNo } from "@/features/lot-transformations/lib/generate-transformation-no";
import { buildAllocationShares, getEffectiveLotAllocations, roundAmount, roundQty } from "@/features/purchase-realization/lib/lot-allocation";
import { recalculatePurchaseRealizationSummary } from "@/features/purchase-realization/lib/recalculate-purchase-realization-summary";
import { getLotTransformationById } from "@/features/lot-transformations/lib/transformation-query";
import { serializeLotTransformationDetail } from "@/features/lot-transformations/lib/serialize-transformation";
import type { LotTransformationPayload } from "@/features/lot-transformations/schemas/lot-transformation.schema";
import { createAuditTrailSafe } from "@/lib/audit-trail";
import { prisma } from "@/lib/prisma";

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

function buildLotCodePrefix(transformationDate: Date, transformationType: string) {
  const yy = String(transformationDate.getUTCFullYear()).slice(-2);
  const mm = String(transformationDate.getUTCMonth() + 1).padStart(2, "0");
  const dd = String(transformationDate.getUTCDate()).padStart(2, "0");
  const typeCode = transformationType === "REGRADE" ? "RGD" : "MIX";
  return `LOT-${yy}${mm}${dd}-${typeCode}`;
}

export async function createLotTransformation(params: {
  payload: LotTransformationPayload;
  actorUserId: string;
  requestMethod: string;
  pathname: string;
}) {
  const parsed = params.payload;
  const transformationDate = new Date(parsed.transformation_date);
  if (Number.isNaN(transformationDate.getTime())) {
    return NextResponse.json(
      {
        message: "Validasi gagal",
        errors: { transformation_date: ["Tanggal transformasi tidak valid"] }
      },
      { status: 422 }
    );
  }

  const sourceLotIds = parsed.inputs.map((input) => parseBigInt(input.source_lot_id));
  const outputGradeIds = parsed.outputs.map((output) => {
    const raw = output.grade_id;
    return {
      raw,
      parsed: raw ? parseBigInt(raw) : null
    };
  });
  const outputWarehouseIds = parsed.outputs.map((output) => parseBigInt(output.warehouse_id));
  const outputLocationIds = parsed.outputs.map((output) => ({
    raw: output.warehouse_location_id,
    parsed: output.warehouse_location_id ? parseBigInt(output.warehouse_location_id) : null
  }));

  if (
    sourceLotIds.some((id) => id === null) ||
    outputWarehouseIds.some((id) => id === null) ||
    outputGradeIds.some((entry) => entry.raw && entry.parsed === null) ||
    outputLocationIds.some((entry) => entry.raw && entry.parsed === null)
  ) {
    return NextResponse.json({ message: "Invalid reference id" }, { status: 400 });
  }

  const lots = await prisma.inventoryLot.findMany({
    where: {
      id: { in: sourceLotIds as bigint[] }
    },
    include: {
      unit: true,
      purchase: {
        select: {
          id: true,
          agentId: true,
          profitShareSchemeId: true,
          profitShareScheme: {
            select: {
              shareAgent: true
            }
          }
        }
      },
      purchaseAllocations: true
    }
  });

  if (lots.length !== sourceLotIds.length) {
    return NextResponse.json({ message: "Sebagian lot sumber tidak ditemukan" }, { status: 404 });
  }

  const lotMap = new Map(lots.map((lot) => [lot.id.toString(), lot]));
  const unitIds = new Set(lots.map((lot) => lot.unitId.toString()));
  if (unitIds.size > 1) {
    return NextResponse.json(
      { message: "Semua lot sumber harus memakai unit yang sama sebelum dilakukan mixing." },
      { status: 422 }
    );
  }

  let totalInputQty = 0;
  let totalInputCost = 0;

  for (const input of parsed.inputs) {
    const lot = lotMap.get(input.source_lot_id);
    if (!lot) {
      return NextResponse.json({ message: "Lot sumber tidak ditemukan" }, { status: 404 });
    }

    const availableQty = lot.availableQty.toNumber();
    if (input.qty_used > availableQty) {
      return NextResponse.json(
        { message: `Qty lot ${lot.lotCode} melebihi available qty ${availableQty}` },
        { status: 422 }
      );
    }

    totalInputQty += input.qty_used;
    totalInputCost += input.qty_used * lot.unitCost.toNumber();
  }

  const totalOutputQty = parsed.outputs.reduce((sum, output) => sum + output.qty_produced, 0);
  if (totalOutputQty > totalInputQty) {
    return NextResponse.json(
      { message: "Total qty hasil tidak boleh melebihi total qty lot sumber." },
      { status: 422 }
    );
  }

  const regradeSourceLot = parsed.transformation_type === "REGRADE" ? lots[0] ?? null : null;
  const unusedSourceRemainderQty =
    parsed.transformation_type === "REGRADE" && regradeSourceLot
      ? Number(
          (
            regradeSourceLot.availableQty.toNumber() -
            (parsed.inputs[0]?.qty_used ?? 0)
          ).toFixed(3)
        )
      : 0;
  const processingLossQty =
    parsed.transformation_type === "REGRADE"
      ? Number((totalInputQty - totalOutputQty).toFixed(3))
      : 0;
  const remainderMode =
    parsed.transformation_type === "REGRADE" && unusedSourceRemainderQty > 0
      ? parsed.remainder_mode ?? null
      : null;
  const processingLossMode =
    parsed.transformation_type === "REGRADE" && processingLossQty > 0
      ? parsed.processing_loss_mode ?? null
      : null;

  if (
    parsed.transformation_type === "REGRADE" &&
    unusedSourceRemainderQty > 0 &&
    !remainderMode
  ) {
    return NextResponse.json(
      {
        message: "Validasi gagal",
        errors: {
          remainder_mode: ["Pilih bagaimana sisa regrade harus dicatat"]
        }
      },
      { status: 422 }
    );
  }

  if (
    parsed.transformation_type === "REGRADE" &&
    processingLossQty > 0 &&
    !processingLossMode
  ) {
    return NextResponse.json(
      {
        message: "Validasi gagal",
        errors: {
          processing_loss_mode: [
            "Selisih hasil regrade harus ditangani: tambahkan output baru atau catat sebagai susut/lost"
          ]
        }
      },
      { status: 422 }
    );
  }

  if (totalOutputQty <= 0) {
    return NextResponse.json(
      { message: "Total qty hasil harus lebih besar dari 0." },
      { status: 422 }
    );
  }

  const weightedUnitCost = Number((totalInputCost / totalOutputQty).toFixed(2));
  const transformationNo = await generateTransformationNo(
    transformationDate,
    parsed.transformation_type
  );
  const lotCodePrefix = buildLotCodePrefix(transformationDate, parsed.transformation_type);
  const existingOutputCount = await prisma.inventoryLot.count({
    where: {
      lotCode: {
        startsWith: lotCodePrefix
      }
    }
  });

  const commonUnitId = lots[0]?.unitId;
  type TransformationTx = Prisma.TransactionClient & {
    lotPurchaseAllocation: typeof prisma.lotPurchaseAllocation;
    purchaseRealizationEntry: typeof prisma.purchaseRealizationEntry;
    purchaseRealizationSummary: typeof prisma.purchaseRealizationSummary;
  };
  const createdId = await prisma.$transaction(async (rawTx) => {
    const tx = rawTx as TransformationTx;
    const transformation = await tx.lotTransformation.create({
      data: {
        transformationNo,
        transformationType: parsed.transformation_type,
        transformationDate,
        remainderMode,
        remainderQty:
          unusedSourceRemainderQty > 0 ? new Prisma.Decimal(unusedSourceRemainderQty) : null,
        processingLossMode,
        processingLossQty:
          processingLossQty > 0 ? new Prisma.Decimal(processingLossQty) : null,
        notes: parsed.notes ?? null,
        createdById: BigInt(params.actorUserId)
      }
    });

    const affectedPurchaseIds = new Set<bigint>();
    const outputLots: Array<{ id: bigint; qtyProduced: number }> = [];
    const sourceContributionRows: Array<{
      sourceLotId: bigint;
      purchaseId: bigint;
      purchaseLineId: bigint | null;
      agentIdSnapshot: bigint | null;
      profitShareSchemeIdSnapshot: bigint | null;
      sourceAllocationId: bigint | null;
      sourceUnitCost: Prisma.Decimal;
      usedQty: number;
    }> = [];

    for (const input of parsed.inputs) {
      const lot = lotMap.get(input.source_lot_id)!;
      const appliedUnusedRemainderQty =
        parsed.transformation_type === "REGRADE" &&
        lot.id === regradeSourceLot?.id &&
        remainderMode === "SHRINKAGE"
          ? unusedSourceRemainderQty
          : 0;
      const appliedProcessingLossQty =
        parsed.transformation_type === "REGRADE" &&
        lot.id === regradeSourceLot?.id &&
        processingLossMode === "SHRINKAGE"
          ? processingLossQty
          : 0;
      const newAvailableQty = Number(
        (
          lot.availableQty.toNumber() -
          input.qty_used -
          appliedUnusedRemainderQty
        ).toFixed(3)
      );

      await tx.lotTransformationInput.create({
        data: {
          transformationId: transformation.id,
          sourceLotId: lot.id,
          qtyUsed: new Prisma.Decimal(input.qty_used),
          unitCostSnapshot: lot.unitCost,
          notes: input.notes ?? null
        }
      });

      await tx.inventoryLot.update({
        where: { id: lot.id },
        data: {
          availableQty: new Prisma.Decimal(newAvailableQty),
          shrinkageQty:
            appliedUnusedRemainderQty > 0 || appliedProcessingLossQty > 0
              ? new Prisma.Decimal(
                  Number(
                    (
                      lot.shrinkageQty.toNumber() +
                      appliedUnusedRemainderQty +
                      appliedProcessingLossQty
                    ).toFixed(3)
                  )
                )
              : lot.shrinkageQty,
          status: newAvailableQty <= 0 ? "DEPLETED" : lot.status
        }
      });

      const effectiveAllocations = getEffectiveLotAllocations(lot);
      const totalAffectedQty = roundQty(
        input.qty_used + appliedUnusedRemainderQty + appliedProcessingLossQty
      );
      const sourceShares = buildAllocationShares(
        effectiveAllocations,
        lot.availableQty.toNumber(),
        totalAffectedQty
      );

      for (const share of sourceShares) {
        affectedPurchaseIds.add(share.allocation.purchaseId);
        const usedQty = roundQty(input.qty_used * share.ratio);
        if (usedQty > 0) {
          sourceContributionRows.push({
            sourceLotId: lot.id,
            purchaseId: share.allocation.purchaseId,
            purchaseLineId: share.allocation.purchaseLineId,
            agentIdSnapshot: share.allocation.agentIdSnapshot,
            profitShareSchemeIdSnapshot: share.allocation.profitShareSchemeIdSnapshot,
            sourceAllocationId: share.allocation.id,
            sourceUnitCost: share.allocation.unitCostSnapshot,
            usedQty
          });
        }

        const shrinkageQty = roundQty(
          (appliedUnusedRemainderQty + appliedProcessingLossQty) * share.ratio
        );
        if (shrinkageQty > 0) {
          await tx.purchaseRealizationEntry.create({
            data: {
              purchaseId: share.allocation.purchaseId,
              lotId: lot.id,
              allocationId: share.allocation.id ?? undefined,
              eventType: "TRANSFORMATION_SHRINKAGE",
              referenceType: "LOT_TRANSFORMATION",
              referenceId: transformation.id,
              occurredAt: transformationDate,
              qtyIn: new Prisma.Decimal(0),
              qtyOut: new Prisma.Decimal(0),
              qtyShrinkage: new Prisma.Decimal(shrinkageQty),
              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 transformation ${transformation.transformationNo}`
            }
          });
        }

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

    for (const [index, output] of parsed.outputs.entries()) {
      const lotCode = `${lotCodePrefix}-${String(existingOutputCount + index + 1).padStart(3, "0")}`;
      const resultLot = await tx.inventoryLot.create({
        data: {
          lotCode,
          sourceType: "TRANSFORMATION",
          sourceRefId: transformation.id,
          purchaseId: null,
          purchaseLineId: null,
          receiptId: null,
          receiptLineId: null,
          gradeId: outputGradeIds[index]?.parsed ?? null,
          warehouseId: outputWarehouseIds[index] as bigint,
          warehouseLocationId: outputLocationIds[index]?.parsed ?? null,
          originalQty: new Prisma.Decimal(output.qty_produced),
          availableQty: new Prisma.Decimal(output.qty_produced),
          reservedQty: new Prisma.Decimal(0),
          damagedQty: new Prisma.Decimal(0),
          shrinkageQty: new Prisma.Decimal(0),
          unitId: commonUnitId!,
          unitCost: new Prisma.Decimal(weightedUnitCost),
          receivedAt: transformationDate,
          status: "ACTIVE",
          qrCodeValue: lotCode,
          barcodeValue: lotCode,
          notes: output.notes ?? parsed.notes ?? null
        }
      });

      await tx.lotTransformationOutput.create({
        data: {
          transformationId: transformation.id,
          resultLotId: resultLot.id,
          qtyProduced: new Prisma.Decimal(output.qty_produced),
          notes: output.notes ?? null
        }
      });

      outputLots.push({
        id: resultLot.id,
        qtyProduced: output.qty_produced
      });
    }

    for (const outputLot of outputLots) {
      for (const contribution of sourceContributionRows) {
        const allocatedQty = roundQty(
          outputLot.qtyProduced * (contribution.usedQty / totalOutputQty)
        );
        if (allocatedQty <= 0) continue;

        await tx.lotPurchaseAllocation.create({
          data: {
            lotId: outputLot.id,
            purchaseId: contribution.purchaseId,
            purchaseLineId: contribution.purchaseLineId,
            sourceType: "TRANSFORMATION",
            sourceRefId: transformation.id,
            agentIdSnapshot: contribution.agentIdSnapshot,
            profitShareSchemeIdSnapshot: contribution.profitShareSchemeIdSnapshot,
            qtyAllocated: new Prisma.Decimal(allocatedQty),
            costTotalAllocated: new Prisma.Decimal(
              roundAmount(allocatedQty * contribution.sourceUnitCost.toNumber())
            ),
            unitCostSnapshot: contribution.sourceUnitCost
          }
        });
      }
    }

    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 transformation.id;
  });

  const created = await getLotTransformationById(createdId);
  if (!created) {
    return NextResponse.json(
      { message: "Transformation created but detail unavailable" },
      { status: 201 }
    );
  }

  await createAuditTrailSafe({
    userId: params.actorUserId,
    action: "LOT_TRANSFORMATION_CREATED",
    entityType: "LOT_TRANSFORMATION",
    entityId: created.id,
    method: params.requestMethod,
    pathname: params.pathname,
    statusCode: 201,
    summary: `Transformation ${created.transformationNo} dibuat`,
    metadata: {
      transformation_no: created.transformationNo,
      type: created.transformationType,
      remainder_mode: created.remainderMode,
      remainder_qty: created.remainderQty?.toNumber() ?? null,
      processing_loss_mode: created.processingLossMode,
      processing_loss_qty: created.processingLossQty?.toNumber() ?? null,
      input_count: created.inputs.length,
      output_count: created.outputs.length
    }
  });

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