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 {
  isJitSalePayloadValidationError,
  parseJitSaleCloseRequest
} from "@/features/sales-jit/lib/parse-jit-sale-request";
import { serializeJitSaleDetail } from "@/features/sales-jit/lib/serialize-jit-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 jitSaleDetailInclude = {
  buyer: true,
  courier: true,
  lines: {
    include: {
      grade: true,
      agent: true,
      profitShareScheme: true
    }
  }
} as const;

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 parseJitSaleCloseRequest(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.jitSale.findUnique({
      where: { id: parsedId },
      include: jitSaleDetailInclude
    });

    if (!existingSale) {
      return NextResponse.json({ message: "Penjualan just in time tidak ditemukan" }, { status: 404 });
    }
    if (existingSale.status === "CLOSED") {
      return NextResponse.json({ message: "Penjualan just in time 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]));
    const exchangeRate = existingSale.exchangeRate?.toNumber() ?? 1;
    let totalNominalBuyer = 0;
    let totalNominalCompany = 0;
    let totalAgentCommission = 0;
    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 qtyActualSold = line.qtyPlanned.toNumber();
      const nominalBuyer = qtyActualSold * payloadLine.selling_price_actual;
      const nominalCompany = nominalBuyer * exchangeRate;
      const actualSellingPriceCompany = payloadLine.selling_price_actual * exchangeRate;
      const malUnitPrice = line.malUnitPrice.toNumber();
      const shareAgent = line.agentSharePercent?.toNumber() ?? 0;
      const commissionBase = (actualSellingPriceCompany - malUnitPrice) * qtyActualSold;
      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
          });
        }
      }
    }

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

    const updated = await prisma.$transaction(async (tx) => {
      for (const payloadLine of payload.lines) {
        const line = lineMap.get(payloadLine.line_id)!;
        await tx.jitSaleLine.update({
          where: { id: line.id },
          data: {
            qtyActualSold: new Prisma.Decimal(roundQty(line.qtyPlanned.toNumber())),
            sellingPriceActual: new Prisma.Decimal(roundAmount(payloadLine.selling_price_actual))
          }
        });
      }

      await tx.jitSale.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.JIT_SALE_COMMISSION,
          amount: adjustment.amount,
          balanceAfter: updatedAgent.currentBalance.toNumber(),
          effectiveDate: closeDate,
          referenceType: "JIT_SALE",
          referenceId: existingSale.id.toString(),
          referenceNo: existingSale.saleNo,
          notes: `Komisi agent dari penjualan just in time ${existingSale.saleNo}`
        });
      }

      return tx.jitSale.findUniqueOrThrow({
        where: { id: existingSale.id },
        include: jitSaleDetailInclude
      });
    });

    await createAuditTrailSafe({
      userId: auth.user.id,
      action: "JIT_SALE_CLOSED",
      entityType: "JIT_SALE",
      entityId: updated.id,
      method: request.method,
      pathname: new URL(request.url).pathname,
      statusCode: 200,
      summary: `Penjualan just in time ${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: serializeJitSaleDetail(updated)
    });
  } catch (error) {
    if (isJitSalePayloadValidationError(error)) {
      return NextResponse.json(
        {
          message: error.message,
          errors: error.errors
        },
        { status: 422 }
      );
    }

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