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

import {
  AGENT_BALANCE_DIRECTIONS,
  AGENT_BALANCE_SOURCES,
  AGENT_BALANCE_TYPES,
  createAgentBalanceMutation,
  roundBalanceAmount
} from "@/features/agents/lib/balance-mutations";
import { recalculatePurchaseRealizationSummary } from "@/features/purchase-realization/lib/recalculate-purchase-realization-summary";
import { generatePurchaseNo } from "@/features/purchases/lib/generate-purchase-no";
import { serializeOfficeBuyoutListItem } from "@/features/purchases/lib/serialize-office-buyout";
import { officeBuyoutInputSchema } from "@/features/purchases/schemas/office-buyout.schema";
import { generateLotCode } from "@/features/receipts/lib/generate-lot-code";
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;
  }
}

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

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

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

const officeBuyoutInclude = {
  buyoutSourceAgent: {
    select: {
      id: true,
      name: true
    }
  },
  lines: {
    include: {
      unit: {
        select: {
          code: true
        }
      },
      sourceLot: {
        select: {
          id: true,
          lotCode: true,
          grade: {
            select: {
              name: true
            }
          }
        }
      }
    }
  },
  lots: {
    select: {
      lotCode: true,
      purchaseLineId: true
    }
  }
} as const;

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

  const purchases = await prisma.purchase.findMany({
    where: {
      purchaseType: "OFFICE_BUYOUT"
    },
    include: officeBuyoutInclude,
    orderBy: [{ createdAt: "desc" }]
  });

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

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

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

  const purchaseDate = new Date(`${parsed.data.purchase_date}T00:00:00.000Z`);
  const sourceAgentId = parseBigInt(parsed.data.buyout_source_agent_id);
  if (sourceAgentId === null || Number.isNaN(purchaseDate.getTime())) {
    return NextResponse.json({ message: "Data referensi atau tanggal tidak valid" }, { status: 400 });
  }

  const sourceLotIds = parsed.data.lines.map((line) => parseBigInt(line.source_lot_id));
  if (sourceLotIds.some((id) => id === null)) {
    return NextResponse.json({ message: "Lot sumber tidak valid" }, { status: 400 });
  }

  const [agent, sourceLots] = await Promise.all([
    prisma.agent.findUnique({
      where: { id: sourceAgentId },
      select: {
        id: true,
        name: true,
        currentBalance: true
      }
    }),
    prisma.inventoryLot.findMany({
      where: {
        id: {
          in: sourceLotIds as bigint[]
        }
      },
      include: {
        purchase: {
          select: {
            id: true,
            agentId: true,
            profitShareSchemeId: true,
            purchaseType: true,
            profitShareScheme: {
              select: {
                shareAgent: true
              }
            }
          }
        },
        purchaseAllocations: true,
        purchaseLine: {
          select: {
            malUnitPrice: true
          }
        },
        grade: true,
        unit: true,
        warehouse: true,
        warehouseLocation: true
      }
    })
  ]);

  if (!agent) {
    return NextResponse.json({ message: "Agen sumber tidak ditemukan" }, { status: 404 });
  }

  const sourceLotMap = new Map(sourceLots.map((lot) => [lot.id.toString(), lot]));
  for (const line of parsed.data.lines) {
    const sourceLot = sourceLotMap.get(line.source_lot_id);
    if (!sourceLot) {
      return NextResponse.json({ message: `Lot ${line.source_lot_id} tidak ditemukan` }, { status: 404 });
    }
    if (sourceLot.purchase?.purchaseType !== "REGULAR" || sourceLot.purchase?.agentId !== sourceAgentId) {
      return NextResponse.json({ message: `Lot ${sourceLot.lotCode} tidak berasal dari agen yang dipilih` }, { status: 422 });
    }
    if (sourceLot.status !== "ACTIVE") {
      return NextResponse.json({ message: `Lot ${sourceLot.lotCode} tidak aktif untuk buyout` }, { status: 422 });
    }
    if (line.qty_buyout > sourceLot.availableQty.toNumber()) {
      return NextResponse.json(
        {
          message: `Qty buyout untuk ${sourceLot.lotCode} melebihi stok tersedia ${sourceLot.availableQty.toNumber()}`
        },
        { status: 422 }
      );
    }
  }

  const purchaseNo = await generatePurchaseNo(purchaseDate);

  const created = await prisma.$transaction(async (rawTx) => {
    const tx = rawTx as OfficeBuyoutTx;
    const purchase = await tx.purchase.create({
      data: {
        purchaseNo,
        purchaseType: "OFFICE_BUYOUT",
        purchaseDate,
        buyoutSourceAgentId: sourceAgentId,
        receivedAt: purchaseDate,
        status: "SUBMITTED",
        notes: parsed.data.notes ?? null,
        createdById: BigInt(auth.user.id)
      }
    });

    const baseLotCode = await generateLotCode(purchaseDate, agent.name);
    const codeMatch = baseLotCode.match(/-(\d+)$/);
    if (!codeMatch) {
      throw new Error("Gagal generate kode lot buyout");
    }
    const lotPrefix = baseLotCode.replace(/-\d+$/, "");
    let lotSuffix = Number(codeMatch[1] ?? "0") || 1;

    let totalAgentCommission = 0;
    const affectedSourcePurchaseIds = new Set<bigint>();

    for (const line of parsed.data.lines) {
      const sourceLot = sourceLotMap.get(line.source_lot_id)!;
      const qtyBuyout = roundQty(line.qty_buyout);
      const buyoutUnitPrice = roundAmount(line.buyout_unit_price);
      const malUnitPrice = sourceLot.purchaseLine?.malUnitPrice?.toNumber() ?? 0;
      const agentSharePercent = sourceLot.purchase?.profitShareScheme?.shareAgent.toNumber() ?? 0;
      const profitAmount = Math.max(0, roundAmount((buyoutUnitPrice - malUnitPrice) * qtyBuyout));
      const agentCommission = roundAmount(profitAmount * (agentSharePercent / 100));
      totalAgentCommission += agentCommission;
      if (sourceLot.purchase?.id) {
        affectedSourcePurchaseIds.add(sourceLot.purchase.id);
      }

      const createdLine = await tx.purchaseLine.create({
        data: {
          purchaseId: purchase.id,
          gradeId: sourceLot.gradeId,
          sourceLotId: sourceLot.id,
          qtyOrdered: new Prisma.Decimal(qtyBuyout),
          qtyReceived: new Prisma.Decimal(qtyBuyout),
          qtyAccepted: new Prisma.Decimal(qtyBuyout),
          qtyRejected: new Prisma.Decimal(0),
          unitId: sourceLot.unitId,
          unitPrice: new Prisma.Decimal(buyoutUnitPrice),
          buyoutMalUnitPriceSnapshot: new Prisma.Decimal(roundAmount(malUnitPrice)),
          buyoutAgentSharePercent: new Prisma.Decimal(roundAmount(agentSharePercent)),
          buyoutProfitAmount: new Prisma.Decimal(profitAmount),
          buyoutAgentCommission: new Prisma.Decimal(agentCommission),
          unitCost: new Prisma.Decimal(buyoutUnitPrice),
          malUnitPrice: sourceLot.purchaseLine?.malUnitPrice ?? null,
          subtotal: new Prisma.Decimal(roundAmount(qtyBuyout * buyoutUnitPrice)),
          classificationStatus: "FINAL",
          warehouseId: sourceLot.warehouseId,
          warehouseLocationId: sourceLot.warehouseLocationId,
          notes: line.notes ?? null
        }
      });

      const lotCode = `${lotPrefix}-${String(lotSuffix).padStart(3, "0")}`;
      lotSuffix += 1;

      const createdLot = await tx.inventoryLot.create({
        data: {
          lotCode,
          parentLotId: sourceLot.id,
          sourceType: "OFFICE_BUYOUT",
          sourceRefId: purchase.id,
          purchaseId: purchase.id,
          purchaseLineId: createdLine.id,
          gradeId: sourceLot.gradeId,
          warehouseId: sourceLot.warehouseId,
          warehouseLocationId: sourceLot.warehouseLocationId,
          originalQty: new Prisma.Decimal(qtyBuyout),
          availableQty: new Prisma.Decimal(qtyBuyout),
          unitId: sourceLot.unitId,
          unitCost: new Prisma.Decimal(buyoutUnitPrice),
          receivedAt: purchaseDate,
          status: "ACTIVE",
          qrCodeValue: lotCode,
          barcodeValue: lotCode,
          notes: `Buyout kantor dari lot ${sourceLot.lotCode}`
        }
      });

      const buyoutAmount = roundAmount(qtyBuyout * buyoutUnitPrice);
      const buyoutAllocation = await tx.lotPurchaseAllocation.create({
        data: {
          lotId: createdLot.id,
          purchaseId: purchase.id,
          purchaseLineId: createdLine.id,
          sourceType: "OFFICE_BUYOUT",
          sourceRefId: purchase.id,
          agentIdSnapshot: null,
          profitShareSchemeIdSnapshot: null,
          qtyAllocated: new Prisma.Decimal(qtyBuyout),
          costTotalAllocated: new Prisma.Decimal(buyoutAmount),
          unitCostSnapshot: new Prisma.Decimal(buyoutUnitPrice),
          notes: `Allocation buyout dari lot ${sourceLot.lotCode}`
        }
      });

      await tx.purchaseRealizationEntry.create({
        data: {
          purchaseId: purchase.id,
          lotId: createdLot.id,
          allocationId: buyoutAllocation.id,
          eventType: "OPENING_COST",
          referenceType: "PURCHASE",
          referenceId: purchase.id,
          occurredAt: purchaseDate,
          qtyIn: new Prisma.Decimal(qtyBuyout),
          qtyOut: new Prisma.Decimal(0),
          qtyShrinkage: new Prisma.Decimal(0),
          amountCost: new Prisma.Decimal(buyoutAmount),
          amountRevenue: new Prisma.Decimal(0),
          amountExpense: new Prisma.Decimal(0),
          amountProfit: new Prisma.Decimal(0),
          agentSharePercentSnapshot: null,
          agentAmount: new Prisma.Decimal(0),
          notes: `Opening realization office buyout ${purchase.purchaseNo}`
        }
      });

      const sourceAllocations =
        sourceLot.purchaseAllocations.length > 0
          ? sourceLot.purchaseAllocations
          : sourceLot.purchase?.id
          ? [
              {
                id: null,
                lotId: sourceLot.id,
                purchaseId: sourceLot.purchase.id,
                purchaseLineId: sourceLot.purchaseLineId,
                sourceType: "PURCHASE",
                sourceRefId: sourceLot.purchase.id,
                agentIdSnapshot: sourceLot.purchase.agentId ?? null,
                profitShareSchemeIdSnapshot: sourceLot.purchase.profitShareSchemeId ?? null,
                qtyAllocated: new Prisma.Decimal(sourceLot.availableQty),
                costTotalAllocated: new Prisma.Decimal(
                  roundAmount(sourceLot.availableQty.toNumber() * sourceLot.unitCost.toNumber())
                ),
                unitCostSnapshot: sourceLot.unitCost,
                notes: null,
                createdAt: new Date(),
                updatedAt: new Date()
              }
            ]
          : [];

      const sourceAvailableQty = sourceLot.availableQty.toNumber();
      for (const allocation of sourceAllocations) {
        const allocationQty = allocation.qtyAllocated.toNumber();
        if (sourceAvailableQty <= 0 || allocationQty <= 0) continue;
        const allocationRatio = allocationQty / sourceAvailableQty;
        const transferredQty = roundQty(qtyBuyout * allocationRatio);
        const transferredRevenue = roundAmount(buyoutAmount * allocationRatio);
        const transferredCost = roundAmount(allocation.unitCostSnapshot.toNumber() * transferredQty);
        const transferredProfit = Math.max(0, roundAmount(transferredRevenue - transferredCost));
        const transferredAgentAmount = roundAmount(transferredProfit * (agentSharePercent / 100));
        const remainingQty = roundQty(Math.max(0, allocationQty - transferredQty));
        const remainingCost = roundAmount(
          Math.max(0, allocation.costTotalAllocated.toNumber() - transferredCost)
        );

        await tx.purchaseRealizationEntry.create({
          data: {
            purchaseId: allocation.purchaseId,
            lotId: sourceLot.id,
            allocationId: allocation.id ?? undefined,
            eventType: "OFFICE_BUYOUT_REVENUE",
            referenceType: "PURCHASE",
            referenceId: purchase.id,
            occurredAt: purchaseDate,
            qtyIn: new Prisma.Decimal(0),
            qtyOut: new Prisma.Decimal(transferredQty),
            qtyShrinkage: new Prisma.Decimal(0),
            amountCost: new Prisma.Decimal(0),
            amountRevenue: new Prisma.Decimal(transferredRevenue),
            amountExpense: new Prisma.Decimal(0),
            amountProfit: new Prisma.Decimal(transferredProfit),
            agentSharePercentSnapshot:
              agentSharePercent > 0 ? new Prisma.Decimal(roundAmount(agentSharePercent)) : null,
            agentAmount: new Prisma.Decimal(transferredAgentAmount),
            notes: `Revenue office buyout ${purchase.purchaseNo} dari lot ${sourceLot.lotCode}`
          }
        });

        if (allocation.id) {
          await tx.lotPurchaseAllocation.update({
            where: { id: allocation.id },
            data: {
              qtyAllocated: new Prisma.Decimal(remainingQty),
              costTotalAllocated: new Prisma.Decimal(remainingCost)
            }
          });
        }
      }

      const nextAvailable = roundQty(sourceLot.availableQty.toNumber() - qtyBuyout);
      await tx.inventoryLot.update({
        where: { id: sourceLot.id },
        data: {
          availableQty: new Prisma.Decimal(nextAvailable),
          status: nextAvailable <= 0 ? "CLOSED" : "ACTIVE"
        }
      });
    }

    if (totalAgentCommission > 0) {
      const nextBalance = roundBalanceAmount(agent.currentBalance.toNumber() + totalAgentCommission);
      await tx.agent.update({
        where: { id: agent.id },
        data: {
          currentBalance: new Prisma.Decimal(nextBalance)
        }
      });
      await createAgentBalanceMutation(tx, {
        agentId: agent.id,
        balanceType: AGENT_BALANCE_TYPES.PROFIT_SHARE,
        direction: AGENT_BALANCE_DIRECTIONS.IN,
        source: AGENT_BALANCE_SOURCES.OFFICE_BUYOUT_COMMISSION,
        amount: totalAgentCommission,
        balanceAfter: nextBalance,
        effectiveDate: purchaseDate,
        referenceType: "PURCHASE",
        referenceId: purchase.id.toString(),
        referenceNo: purchase.purchaseNo,
        notes: `Komisi buyout kantor ${purchase.purchaseNo}`,
        metadata: {
          purchase_type: "OFFICE_BUYOUT",
          source_agent_name: agent.name
        }
      });
    }

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

    return tx.purchase.findUniqueOrThrow({
      where: { id: purchase.id },
      include: officeBuyoutInclude
    });
  });

  await createAuditTrailSafe({
    userId: auth.user.id,
    action: "OFFICE_BUYOUT_CREATED",
    entityType: "PURCHASE",
    entityId: created.id,
    method: request.method,
    pathname: new URL(request.url).pathname,
    statusCode: 201,
    summary: `Buyout kantor ${created.purchaseNo} dibuat`,
    metadata: {
      purchase_type: "OFFICE_BUYOUT",
      purchase_no: created.purchaseNo,
      source_agent_name: agent.name,
      line_count: created.lines.length
    }
  });

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