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 {
  isRegularSalePayloadValidationError,
  parseRegularSaleCloseRequest
} from "@/features/sales-regular/lib/parse-regular-sale-request";
import { buildAllocationShares, getEffectiveLotAllocations, roundAmount as roundAllocationAmount } from "@/features/purchase-realization/lib/lot-allocation";
import { recalculatePurchaseRealizationSummary } from "@/features/purchase-realization/lib/recalculate-purchase-realization-summary";
import { serializeRegularSaleDetail } from "@/features/sales-regular/lib/serialize-regular-sale";
import {
  storeRegularSaleReceipt,
  validateRegularSaleReceiptFile
} from "@/features/sales-regular/lib/store-shipping-receipt";
import { createAuditTrailSafe } from "@/lib/audit-trail";
import { requireApiAccess } from "@/lib/authorization";
import { prisma } from "@/lib/prisma";

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

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

function roundAmount(value: number) {
  return Number(value.toFixed(2));
}

function roundQty(value: number) {
  return Number(value.toFixed(3));
}

function createAgentBalanceKey(agentId: bigint) {
  return agentId.toString();
}

const regularSaleDetailInclude = {
  buyer: true,
  courier: true,
  lines: {
    include: {
      lot: {
        include: {
          purchase: {
            select: {
              id: true,
              agentId: true,
              profitShareSchemeId: true,
              profitShareScheme: {
                select: {
                  shareAgent: true
                }
              }
            }
          },
          purchaseAllocations: true,
          grade: true,
          unit: true,
          warehouse: true,
          warehouseLocation: true
        }
      }
    }
  }
} as const;

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

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

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

    const { payload, shippingReceiptFile } = await parseRegularSaleCloseRequest(request);
    const closeDate = new Date(`${payload.close_date}T00:00:00.000Z`);
    if (Number.isNaN(closeDate.getTime())) {
      return NextResponse.json({ message: "Tanggal penyelesaian tidak valid" }, { status: 400 });
    }

    if (shippingReceiptFile) {
      const fileError = validateRegularSaleReceiptFile(shippingReceiptFile);
      if (fileError) {
        return NextResponse.json({ message: fileError }, { status: 422 });
      }
    }

    const existingSale = await prisma.regularSale.findUnique({
      where: { id: parsedId },
      include: {
        buyer: true,
        courier: true,
        lines: {
          include: {
            lot: {
              include: {
                grade: true,
                unit: true,
                warehouse: true,
                warehouseLocation: true
              }
            }
          }
        }
      }
    });

    if (!existingSale) {
      return NextResponse.json({ message: "Penjualan reguler tidak ditemukan" }, { status: 404 });
    }
    if (existingSale.status === "CLOSED") {
      return NextResponse.json({ message: "Penjualan reguler sudah ditutup" }, { status: 409 });
    }

    const payloadLineIds = new Set(payload.lines.map((line) => line.line_id));
    if (payloadLineIds.size !== existingSale.lines.length || payload.lines.length !== existingSale.lines.length) {
      return NextResponse.json(
        { message: "Semua item penjualan wajib ditutup dalam satu proses" },
        { status: 422 }
      );
    }

    const lineMap = new Map(existingSale.lines.map((line) => [line.id.toString(), line]));
    let totalNominalBuyer = 0;
    let totalNominalCompany = 0;
    let totalAgentCommission = 0;
    const exchangeRate = existingSale.exchangeRate?.toNumber() ?? 1;
    const agentBalanceAdjustments = new Map<string, { agentId: bigint; amount: number }>();

    for (const payloadLine of payload.lines) {
      const line = lineMap.get(payloadLine.line_id);
      if (!line) {
        return NextResponse.json(
          { message: `Line ${payloadLine.line_id} tidak terdaftar pada penjualan ini` },
          { status: 422 }
        );
      }

      const qtyPlanned = line.qtyPlanned.toNumber();
      const resolvedQty = payloadLine.qty_actual_sold + payloadLine.qty_returned;
      if (resolvedQty > qtyPlanned) {
        return NextResponse.json(
          {
            message: `Berat jual aktual + retur untuk ${line.lot.lotCode} tidak boleh melebihi berat jual`
          },
          { status: 422 }
        );
      }

      const qtyShrinkage = qtyPlanned - payloadLine.qty_actual_sold - payloadLine.qty_returned;
      const nominalBuyer = payloadLine.qty_actual_sold * payloadLine.selling_price_actual;
      const nominalCompany = nominalBuyer * exchangeRate;
      const actualSellingPriceCompany = payloadLine.selling_price_actual * exchangeRate;
      const malUnitPrice = line.malUnitPriceSnapshot?.toNumber() ?? 0;
      const shareAgent = line.agentSharePercent?.toNumber() ?? 0;
      const commissionBase = (actualSellingPriceCompany - malUnitPrice) * payloadLine.qty_actual_sold;
      const lineAgentCommission = roundAmount(commissionBase * (shareAgent / 100));

      totalNominalBuyer += nominalBuyer;
      totalNominalCompany += nominalCompany;
      totalAgentCommission += lineAgentCommission;

      if (line.agentId && lineAgentCommission > 0) {
        const key = createAgentBalanceKey(line.agentId);
        const current = agentBalanceAdjustments.get(key);
        if (current) {
          current.amount = roundAmount(current.amount + lineAgentCommission);
        } else {
          agentBalanceAdjustments.set(key, {
            agentId: line.agentId,
            amount: lineAgentCommission
          });
        }
      }

      if (qtyShrinkage < 0) {
        return NextResponse.json(
          { message: `Perhitungan susut untuk ${line.lot.lotCode} tidak valid` },
          { status: 422 }
        );
      }
    }

    const shippingReceiptFileUrl = shippingReceiptFile
      ? await storeRegularSaleReceipt(shippingReceiptFile)
      : existingSale.shippingReceiptFileUrl;

    const updated = await prisma.$transaction(async (rawTx) => {
      const tx = rawTx as RegularSaleTx;
      const affectedPurchaseIds = new Set<bigint>();
      for (const payloadLine of payload.lines) {
        const line = lineMap.get(payloadLine.line_id)!;
        const qtyShrinkage = roundQty(
          line.qtyPlanned.toNumber() - payloadLine.qty_actual_sold - payloadLine.qty_returned
        );
        const nextAvailable = roundQty(line.lot.availableQty.toNumber() + payloadLine.qty_returned);
        const nextShrinkage = roundQty(line.lot.shrinkageQty.toNumber() + qtyShrinkage);

        await tx.inventoryLot.update({
          where: { id: line.lotId },
          data: {
            availableQty: new Prisma.Decimal(nextAvailable),
            shrinkageQty: new Prisma.Decimal(nextShrinkage),
            status: nextAvailable <= 0 ? "DEPLETED" : "ACTIVE"
          }
        });

        await tx.regularSaleLine.update({
          where: { id: line.id },
          data: {
            qtyActualSold: new Prisma.Decimal(roundQty(payloadLine.qty_actual_sold)),
            qtyReturned: new Prisma.Decimal(roundQty(payloadLine.qty_returned)),
            qtyShrinkage: new Prisma.Decimal(qtyShrinkage),
            sellingPriceActual: new Prisma.Decimal(roundAmount(payloadLine.selling_price_actual))
          }
        });

        const allocations = getEffectiveLotAllocations(line.lot);
        const allocationBaseQty = allocations.reduce(
          (sum, allocation) => sum + allocation.qtyAllocated.toNumber(),
          0
        );
        const soldAndShrinkageQty = roundQty(payloadLine.qty_actual_sold + qtyShrinkage);
        const shares = buildAllocationShares(
          allocations,
          allocationBaseQty,
          soldAndShrinkageQty
        );
        const lineRevenue = roundAmount(payloadLine.qty_actual_sold * payloadLine.selling_price_actual * exchangeRate);

        for (const share of shares) {
          affectedPurchaseIds.add(share.allocation.purchaseId);

          const soldQtyShare =
            soldAndShrinkageQty > 0
              ? roundQty(payloadLine.qty_actual_sold * (share.affectedAllocationQty / soldAndShrinkageQty))
              : 0;
          const shrinkageQtyShare =
            soldAndShrinkageQty > 0
              ? roundQty(qtyShrinkage * (share.affectedAllocationQty / soldAndShrinkageQty))
              : 0;
          const revenueShare = roundAllocationAmount(
            lineRevenue * (share.affectedAllocationQty / soldAndShrinkageQty || 0)
          );

          if (soldQtyShare > 0 || revenueShare > 0) {
            await tx.purchaseRealizationEntry.create({
              data: {
                purchaseId: share.allocation.purchaseId,
                lotId: line.lotId,
                allocationId: share.allocation.id ?? undefined,
                eventType: "SALE_REVENUE",
                referenceType: "REGULAR_SALE",
                referenceId: existingSale.id,
                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 regular sale ${existingSale.saleNo}`
              }
            });
          }

          if (payloadLine.qty_returned > 0) {
            const returnQtyShare = roundQty(
              payloadLine.qty_returned * (share.allocationQty / allocationBaseQty || 0)
            );
            if (returnQtyShare > 0) {
              await tx.purchaseRealizationEntry.create({
                data: {
                  purchaseId: share.allocation.purchaseId,
                  lotId: line.lotId,
                  allocationId: share.allocation.id ?? undefined,
                  eventType: "SALE_RETURN",
                  referenceType: "REGULAR_SALE",
                  referenceId: existingSale.id,
                  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 regular sale ${existingSale.saleNo}`
                }
              });
            }
          }

          if (shrinkageQtyShare > 0) {
            await tx.purchaseRealizationEntry.create({
              data: {
                purchaseId: share.allocation.purchaseId,
                lotId: line.lotId,
                allocationId: share.allocation.id ?? undefined,
                eventType: "SALE_SHRINKAGE",
                referenceType: "REGULAR_SALE",
                referenceId: existingSale.id,
                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 regular sale ${existingSale.saleNo}`
              }
            });
          }

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

      await tx.regularSale.update({
        where: { id: existingSale.id },
        data: {
          closeDate,
          shippingReceiptFileUrl,
          totalNominalBuyer: new Prisma.Decimal(roundAmount(totalNominalBuyer)),
          totalNominalCompany: new Prisma.Decimal(roundAmount(totalNominalCompany)),
          totalAgentCommission: new Prisma.Decimal(roundAmount(totalAgentCommission)),
          status: "CLOSED"
        }
      });

      for (const adjustment of agentBalanceAdjustments.values()) {
        const updatedAgent = await tx.agent.update({
          where: { id: adjustment.agentId },
          data: {
            currentBalance: {
              increment: new Prisma.Decimal(adjustment.amount)
            }
          },
          select: {
            currentBalance: true
          }
        });

        await createAgentBalanceMutation(tx, {
          agentId: adjustment.agentId,
          balanceType: AGENT_BALANCE_TYPES.PROFIT_SHARE,
          direction: AGENT_BALANCE_DIRECTIONS.IN,
          source: AGENT_BALANCE_SOURCES.REGULAR_SALE_COMMISSION,
          amount: adjustment.amount,
          balanceAfter: updatedAgent.currentBalance.toNumber(),
          effectiveDate: closeDate,
          referenceType: "REGULAR_SALE",
          referenceId: existingSale.id.toString(),
          referenceNo: existingSale.saleNo,
          notes: `Komisi agent dari penjualan reguler ${existingSale.saleNo}`
        });
      }

      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.regularSale.findUniqueOrThrow({
        where: { id: existingSale.id },
        include: regularSaleDetailInclude
      });
    });

    await createAuditTrailSafe({
      userId: auth.user.id,
      action: "REGULAR_SALE_CLOSED",
      entityType: "REGULAR_SALE",
      entityId: updated.id,
      method: request.method,
      pathname: new URL(request.url).pathname,
      statusCode: 200,
      summary: `Penjualan reguler ${updated.saleNo} ditutup`,
      metadata: {
        sale_no: updated.saleNo,
        close_date: updated.closeDate?.toISOString().slice(0, 10) ?? null,
        total_nominal_buyer: updated.totalNominalBuyer.toNumber(),
        total_agent_commission: updated.totalAgentCommission.toNumber()
      }
    });

    return NextResponse.json({
      data: serializeRegularSaleDetail(updated)
    });
  } catch (error) {
    if (isRegularSalePayloadValidationError(error)) {
      return NextResponse.json(
        {
          message: error.message,
          errors: error.errors
        },
        { status: 422 }
      );
    }

    return NextResponse.json(
      {
        message: error instanceof Error ? error.message : "Gagal menutup penjualan reguler"
      },
      { status: 500 }
    );
  }
}
