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

import {
  AGENT_BALANCE_DIRECTIONS,
  AGENT_BALANCE_SOURCES,
  AGENT_BALANCE_TYPES,
  createAgentBalanceMutation
} from "@/features/agents/lib/balance-mutations";
import { consignmentCloseLineSchema } from "@/features/consignments/schemas/consignment.schema";
import {
  createSalesCommissionMutation,
  SALES_COMMISSION_DIRECTIONS,
  SALES_COMMISSION_SOURCES
} from "@/features/sales/lib/commission-mutations";
import { buildAllocationShares, getEffectiveLotAllocations, roundAmount } from "@/features/purchase-realization/lib/lot-allocation";
import { recalculatePurchaseRealizationSummary } from "@/features/purchase-realization/lib/recalculate-purchase-realization-summary";
import { createAuditTrailSafe } from "@/lib/audit-trail";
import { requireApiAccess } from "@/lib/authorization";
import { prisma } from "@/lib/prisma";

type RouteContext = { params: Promise<{ lineId: string }> };
type ConsignmentTx = Prisma.TransactionClient & {
  purchaseRealizationEntry: typeof prisma.purchaseRealizationEntry;
  purchaseRealizationSummary: typeof prisma.purchaseRealizationSummary;
  lotPurchaseAllocation: typeof prisma.lotPurchaseAllocation;
};

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

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

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

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

  const closeDate = new Date(`${parsed.data.close_date}T00:00:00.000Z`);
  if (Number.isNaN(closeDate.getTime())) {
    return NextResponse.json({ message: "Tanggal close tidak valid" }, { status: 400 });
  }

  const existingLine = await prisma.consignmentLine.findUnique({
    where: { id: parsedLineId },
    include: {
      lot: {
        include: {
          purchase: {
            select: {
              id: true,
              agentId: true,
              profitShareSchemeId: true,
              agent: {
                select: {
                  name: true
                }
              },
              profitShareScheme: {
                select: {
                  shareAgent: true
                }
              }
            }
          },
          purchaseAllocations: true
        }
      },
      consignment: true
    }
  });

  if (!existingLine) {
    return NextResponse.json({ message: "Item titip jual tidak ditemukan" }, { status: 404 });
  }
  if (existingLine.status === "CLOSED") {
    return NextResponse.json({ message: "Item sudah ditutup" }, { status: 409 });
  }

  const qtyConsigned = existingLine.qtyConsigned.toNumber();
  const qtySold = parsed.data.qty_sold;
  const qtyReturned = parsed.data.qty_returned;
  const salesCommission = parsed.data.sales_commission;
  const resolvedQty = qtySold + qtyReturned;

  if (resolvedQty > qtyConsigned) {
    return NextResponse.json(
      { message: "Berat terjual + kembali tidak boleh melebihi berat titip" },
      { status: 422 }
    );
  }

  const qtyShrinkage = Number((qtyConsigned - qtySold - qtyReturned).toFixed(3));
  const malUnitPrice = existingLine.malUnitPriceSnapshot?.toNumber() ?? 0;
  const shareAgent =
    existingLine.agentSharePercent?.toNumber() ??
    existingLine.lot.purchase?.profitShareScheme?.shareAgent.toNumber() ??
    0;
  const agentCommission = Number(
    ((((qtySold * (parsed.data.selling_price - malUnitPrice)) - salesCommission) * shareAgent) / 100).toFixed(2)
  );

  const updated = await prisma.$transaction(async (rawTx) => {
    const tx = rawTx as ConsignmentTx;
    const nextAvailable = Number((existingLine.lot.availableQty.toNumber() + qtyReturned).toFixed(3));
    await tx.inventoryLot.update({
      where: { id: existingLine.lotId },
      data: {
        availableQty: new Prisma.Decimal(nextAvailable),
        shrinkageQty: new Prisma.Decimal(
          Number((existingLine.lot.shrinkageQty.toNumber() + qtyShrinkage).toFixed(3))
        ),
        status: nextAvailable <= 0 ? "DEPLETED" : "ACTIVE"
      }
    });

    const line = await tx.consignmentLine.update({
      where: { id: parsedLineId },
      data: {
        status: "CLOSED",
        closeDate,
        sellingPrice: new Prisma.Decimal(parsed.data.selling_price),
        qtySold: new Prisma.Decimal(qtySold),
        qtyReturned: new Prisma.Decimal(qtyReturned),
        qtyShrinkage: new Prisma.Decimal(qtyShrinkage),
        salesCommission: new Prisma.Decimal(salesCommission),
        agentCommission: new Prisma.Decimal(agentCommission)
      }
    });

    const affectedPurchaseIds = new Set<bigint>();
    const allocations = getEffectiveLotAllocations(existingLine.lot);
    const allocationBaseQty = allocations.reduce(
      (sum, allocation) => sum + allocation.qtyAllocated.toNumber(),
      0
    );
    const soldAndShrinkageQty = Number((qtySold + qtyShrinkage).toFixed(3));
    const revenueNet = roundAmount((qtySold * parsed.data.selling_price) - salesCommission);
    const shares = buildAllocationShares(allocations, allocationBaseQty, soldAndShrinkageQty);

    for (const share of shares) {
      affectedPurchaseIds.add(share.allocation.purchaseId);
      const soldQtyShare =
        soldAndShrinkageQty > 0
          ? Number((qtySold * (share.affectedAllocationQty / soldAndShrinkageQty)).toFixed(3))
          : 0;
      const shrinkageQtyShare =
        soldAndShrinkageQty > 0
          ? Number((qtyShrinkage * (share.affectedAllocationQty / soldAndShrinkageQty)).toFixed(3))
          : 0;
      const revenueShare = roundAmount(
        soldAndShrinkageQty > 0 ? revenueNet * (share.affectedAllocationQty / soldAndShrinkageQty) : 0
      );

      if (soldQtyShare > 0 || revenueShare > 0) {
        await tx.purchaseRealizationEntry.create({
          data: {
            purchaseId: share.allocation.purchaseId,
            lotId: existingLine.lotId,
            allocationId: share.allocation.id ?? undefined,
            eventType: "CONSIGNMENT_REVENUE",
            referenceType: "CONSIGNMENT",
            referenceId: existingLine.consignmentId,
            occurredAt: closeDate,
            qtyIn: new Prisma.Decimal(0),
            qtyOut: new Prisma.Decimal(soldQtyShare),
            qtyShrinkage: new Prisma.Decimal(0),
            amountCost: new Prisma.Decimal(0),
            amountRevenue: new Prisma.Decimal(revenueShare),
            amountExpense: new Prisma.Decimal(0),
            amountProfit: new Prisma.Decimal(0),
            agentSharePercentSnapshot: null,
            agentAmount: new Prisma.Decimal(0),
            notes: `Revenue consignment ${existingLine.consignment.consignmentNo}`
          }
        });
      }

      if (qtyReturned > 0) {
        const returnQtyShare = Number(
          (qtyReturned * ((allocationBaseQty > 0 ? share.allocationQty / allocationBaseQty : 0))).toFixed(3)
        );
        if (returnQtyShare > 0) {
          await tx.purchaseRealizationEntry.create({
            data: {
              purchaseId: share.allocation.purchaseId,
              lotId: existingLine.lotId,
              allocationId: share.allocation.id ?? undefined,
              eventType: "CONSIGNMENT_RETURN",
              referenceType: "CONSIGNMENT",
              referenceId: existingLine.consignmentId,
              occurredAt: closeDate,
              qtyIn: new Prisma.Decimal(returnQtyShare),
              qtyOut: new Prisma.Decimal(0),
              qtyShrinkage: new Prisma.Decimal(0),
              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: `Return consignment ${existingLine.consignment.consignmentNo}`
            }
          });
        }
      }

      if (shrinkageQtyShare > 0) {
        await tx.purchaseRealizationEntry.create({
          data: {
            purchaseId: share.allocation.purchaseId,
            lotId: existingLine.lotId,
            allocationId: share.allocation.id ?? undefined,
            eventType: "CONSIGNMENT_SHRINKAGE",
            referenceType: "CONSIGNMENT",
            referenceId: existingLine.consignmentId,
            occurredAt: closeDate,
            qtyIn: new Prisma.Decimal(0),
            qtyOut: new Prisma.Decimal(0),
            qtyShrinkage: new Prisma.Decimal(shrinkageQtyShare),
            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 consignment ${existingLine.consignment.consignmentNo}`
          }
        });
      }

      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())
            )
          }
        });
      }
    }

    if (salesCommission > 0) {
      const updatedSales = await tx.sales.update({
        where: { id: existingLine.consignment.salesId },
        data: {
          commissionBalance: {
            increment: new Prisma.Decimal(salesCommission)
          }
        },
        select: {
          commissionBalance: true
        }
      });

      await createSalesCommissionMutation(tx, {
        salesId: existingLine.consignment.salesId,
        source: SALES_COMMISSION_SOURCES.CONSIGNMENT_COMMISSION,
        direction: SALES_COMMISSION_DIRECTIONS.IN,
        amount: salesCommission,
        balanceAfter: updatedSales.commissionBalance.toNumber(),
        effectiveDate: closeDate,
        referenceType: "CONSIGNMENT",
        referenceId: existingLine.consignmentId.toString(),
        referenceNo: existingLine.consignment.consignmentNo,
        notes: `Komisi sales dari titip jual ${existingLine.consignment.consignmentNo}`,
        metadata: {
          line_id: existingLine.id.toString(),
          lot_code: existingLine.lot.lotCode
        }
      });
    }

    if (existingLine.lot.purchase?.agentId && agentCommission > 0) {
      const updatedAgent = await tx.agent.update({
        where: { id: existingLine.lot.purchase.agentId },
        data: {
          currentBalance: {
            increment: new Prisma.Decimal(agentCommission)
          }
        },
        select: {
          currentBalance: true
        }
      });

      await createAgentBalanceMutation(tx, {
        agentId: existingLine.lot.purchase.agentId,
        balanceType: AGENT_BALANCE_TYPES.PROFIT_SHARE,
        direction: AGENT_BALANCE_DIRECTIONS.IN,
        source: AGENT_BALANCE_SOURCES.CONSIGNMENT_COMMISSION,
        amount: agentCommission,
        balanceAfter: updatedAgent.currentBalance.toNumber(),
        effectiveDate: closeDate,
        referenceType: "CONSIGNMENT",
        referenceId: existingLine.consignmentId.toString(),
        referenceNo: existingLine.consignment.consignmentNo,
        notes: `Komisi agent dari titip jual ${existingLine.consignment.consignmentNo}`
      });
    }

    const openLineCount = await tx.consignmentLine.count({
      where: {
        consignmentId: existingLine.consignmentId,
        status: {
          not: "CLOSED"
        }
      }
    });

    await tx.consignment.update({
      where: { id: existingLine.consignmentId },
      data: {
        status: openLineCount === 0 ? "CLOSED" : "PARTIAL_CLOSED"
      }
    });

    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 line;
  });

  await createAuditTrailSafe({
    userId: auth.user.id,
    action: "CONSIGNMENT_LINE_CLOSED",
    entityType: "CONSIGNMENT_LINE",
    entityId: updated.id,
    method: request.method,
    pathname: new URL(request.url).pathname,
    statusCode: 200,
    summary: `Item titip jual ${existingLine.lot.lotCode} ditutup`,
    metadata: {
      consignment_no: existingLine.consignment.consignmentNo,
      lot_code: existingLine.lot.lotCode,
      qty_sold: qtySold,
      qty_returned: qtyReturned,
      qty_shrinkage: qtyShrinkage,
      sales_commission: salesCommission,
      agent_commission: agentCommission
    }
  });

  return NextResponse.json({ success: true });
}
