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

import { ensureDefaultCurrencies } from "@/features/currencies/lib/default-currencies";
import { generateRegularSaleNo } from "@/features/sales-regular/lib/generate-regular-sale-no";
import {
  isRegularSalePayloadValidationError,
  parseRegularSaleCreateRequest
} from "@/features/sales-regular/lib/parse-regular-sale-request";
import {
  serializeRegularSaleDetail,
  serializeRegularSaleListItem
} 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";

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

const regularSaleInclude = {
  buyer: true,
  lines: true
} as const;

const regularSaleDetailInclude = {
  buyer: true,
  courier: true,
  lines: {
    include: {
      lot: {
        include: {
          grade: true,
          unit: true,
          warehouse: true,
          warehouseLocation: true
        }
      }
    }
  }
} as const;

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

  const sales = await prisma.regularSale.findMany({
    include: regularSaleInclude,
    orderBy: [{ createdAt: "desc" }]
  });

  return NextResponse.json({
    data: sales.map(serializeRegularSaleListItem)
  });
}

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

  try {
    await ensureDefaultCurrencies();
    const { payload, shippingReceiptFile } = await parseRegularSaleCreateRequest(request);

    const buyerId = parseBigInt(payload.buyer_id);
    const courierId = payload.courier_id ? parseBigInt(payload.courier_id) : null;
    const saleDate = new Date(`${payload.sale_date}T00:00:00.000Z`);
    const buyerCurrencyCode = payload.buyer_currency_code.trim().toUpperCase();
    const companyCurrencyCode = payload.company_currency_code.trim().toUpperCase();

    if (
      buyerId === null ||
      (payload.courier_id && courierId === null) ||
      Number.isNaN(saleDate.getTime())
    ) {
      return NextResponse.json({ message: "Invalid reference id or date" }, { status: 400 });
    }

    const lotIds = payload.lines.map((line) => parseBigInt(line.lot_id));
    if (lotIds.some((id) => id === null)) {
      return NextResponse.json({ message: "Lot tidak valid" }, { status: 400 });
    }

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

    const [buyer, courier, currencies, lots] = await Promise.all([
      prisma.buyer.findUnique({ where: { id: buyerId } }),
      courierId ? prisma.courier.findUnique({ where: { id: courierId } }) : Promise.resolve(null),
      prisma.currency.findMany({
        where: {
          code: {
            in: [buyerCurrencyCode, companyCurrencyCode]
          },
          status: "ACTIVE"
        }
      }),
      prisma.inventoryLot.findMany({
        where: {
          id: {
            in: lotIds as bigint[]
          }
        },
        include: {
          purchase: {
            select: {
              agentId: true,
              agent: {
                select: {
                  name: true
                }
              },
              profitShareScheme: {
                select: {
                  shareAgent: true
                }
              }
            }
          },
          purchaseLine: {
            select: {
              malUnitPrice: true
            }
          }
        }
      })
    ]);

    if (!buyer) {
      return NextResponse.json({ message: "Buyer tidak ditemukan" }, { status: 404 });
    }
    if (payload.courier_id && !courier) {
      return NextResponse.json({ message: "Kurir tidak ditemukan" }, { status: 404 });
    }
    if (currencies.length !== 2 && buyerCurrencyCode !== companyCurrencyCode) {
      return NextResponse.json({ message: "Currency tidak valid" }, { status: 422 });
    }
    if (currencies.length !== 1 && buyerCurrencyCode === companyCurrencyCode) {
      return NextResponse.json({ message: "Currency tidak valid" }, { status: 422 });
    }

    const exchangeRate =
      buyerCurrencyCode === companyCurrencyCode ? 1 : Number(payload.exchange_rate ?? 0);
    const lotMap = new Map(lots.map((lot) => [lot.id.toString(), lot]));

    let totalNominalBuyer = 0;
    let totalNominalCompany = 0;
    let totalAgentCommission = 0;

    for (const line of payload.lines) {
      const lot = lotMap.get(line.lot_id);
      if (!lot) {
        return NextResponse.json({ message: `Lot ${line.lot_id} tidak ditemukan` }, { status: 404 });
      }
      if (lot.status !== "ACTIVE") {
        return NextResponse.json(
          { message: `Lot ${lot.lotCode} tidak aktif untuk penjualan reguler` },
          { status: 422 }
        );
      }
      if (line.qty_planned > lot.availableQty.toNumber()) {
        return NextResponse.json(
          {
            message: `Berat jual untuk ${lot.lotCode} melebihi stok tersedia ${lot.availableQty.toNumber()}`
          },
          { status: 422 }
        );
      }

      const nominalBuyer = line.qty_planned * line.selling_price_planned;
      const nominalCompany = nominalBuyer * exchangeRate;
      const plannedSellingPriceCompany = line.selling_price_planned * exchangeRate;
      const malUnitPrice = lot.purchaseLine?.malUnitPrice?.toNumber() ?? 0;
      const shareAgent = lot.purchase?.profitShareScheme?.shareAgent.toNumber() ?? 0;
      const commissionBase = (plannedSellingPriceCompany - malUnitPrice) * line.qty_planned;

      totalNominalBuyer += nominalBuyer;
      totalNominalCompany += nominalCompany;
      totalAgentCommission += commissionBase * (shareAgent / 100);
    }

    const shippingCostCompany = payload.shipping_cost_buyer * exchangeRate;
    const shippingReceiptFileUrl = shippingReceiptFile
      ? await storeRegularSaleReceipt(shippingReceiptFile)
      : null;
    const saleNo = await generateRegularSaleNo(saleDate);

    const created = await prisma.$transaction(async (tx) => {
      const sale = await tx.regularSale.create({
        data: {
          saleNo,
          saleDate,
          buyerId,
          buyerCurrencyCode,
          companyCurrencyCode,
          exchangeRate:
            buyerCurrencyCode === companyCurrencyCode ? null : new Prisma.Decimal(exchangeRate),
          courierId,
          shippingCostBuyer: new Prisma.Decimal(roundAmount(payload.shipping_cost_buyer)),
          shippingCostCompany: new Prisma.Decimal(roundAmount(shippingCostCompany)),
          shippingReceiptFileUrl,
          totalNominalBuyer: new Prisma.Decimal(roundAmount(totalNominalBuyer)),
          totalNominalCompany: new Prisma.Decimal(roundAmount(totalNominalCompany)),
          totalAgentCommission: new Prisma.Decimal(roundAmount(totalAgentCommission)),
          notes: payload.notes ?? null,
          createdById: BigInt(auth.user.id),
          lines: {
            create: payload.lines.map((line) => {
              const lot = lotMap.get(line.lot_id)!;
              return {
                lotId: lot.id,
                availableQtySnapshot: lot.availableQty,
                malUnitPriceSnapshot: lot.purchaseLine?.malUnitPrice ?? null,
                agentId: lot.purchase?.agentId ?? null,
                agentNameSnapshot: lot.purchase?.agent?.name ?? null,
                agentSharePercent: lot.purchase?.profitShareScheme?.shareAgent ?? null,
                qtyPlanned: new Prisma.Decimal(roundQty(line.qty_planned)),
                sellingPricePlanned: new Prisma.Decimal(roundAmount(line.selling_price_planned)),
                notes: line.notes ?? null
              };
            })
          }
        }
      });

      for (const line of payload.lines) {
        const lot = lotMap.get(line.lot_id)!;
        const nextAvailable = roundQty(lot.availableQty.toNumber() - line.qty_planned);
        await tx.inventoryLot.update({
          where: { id: lot.id },
          data: {
            availableQty: new Prisma.Decimal(nextAvailable),
            status: nextAvailable <= 0 ? "DEPLETED" : "ACTIVE"
          }
        });
      }

      return tx.regularSale.findUniqueOrThrow({
        where: { id: sale.id },
        include: regularSaleDetailInclude
      });
    });

    await createAuditTrailSafe({
      userId: auth.user.id,
      action: "REGULAR_SALE_CREATED",
      entityType: "REGULAR_SALE",
      entityId: created.id,
      method: request.method,
      pathname: new URL(request.url).pathname,
      statusCode: 201,
      summary: `Penjualan reguler ${created.saleNo} dibuat`,
      metadata: {
        sale_no: created.saleNo,
        buyer_name: created.buyer.name,
        line_count: created.lines.length,
        buyer_currency_code: created.buyerCurrencyCode,
        company_currency_code: created.companyCurrencyCode
      }
    });

    return NextResponse.json(
      {
        data: serializeRegularSaleDetail(created)
      },
      { status: 201 }
    );
  } 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 menyimpan penjualan reguler"
      },
      { status: 500 }
    );
  }
}
