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

import { ensureDefaultCurrencies } from "@/features/currencies/lib/default-currencies";
import { generateJitSaleNo } from "@/features/sales-jit/lib/generate-jit-sale-no";
import {
  isJitSalePayloadValidationError,
  parseJitSaleCreateRequest
} from "@/features/sales-jit/lib/parse-jit-sale-request";
import {
  serializeJitSaleDetail,
  serializeJitSaleListItem
} 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";

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 jitSaleInclude = {
  buyer: true,
  lines: true
} as const;

const jitSaleDetailInclude = {
  buyer: true,
  courier: true,
  lines: {
    include: {
      grade: true,
      agent: true,
      profitShareScheme: true
    }
  }
} as const;

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

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

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

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

  try {
    await ensureDefaultCurrencies();
    const { payload, shippingReceiptFile } = await parseJitSaleCreateRequest(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 gradeIds = payload.lines.map((line) => parseBigInt(line.grade_id));
    const agentIds = payload.lines
      .map((line) => (line.agent_id ? parseBigInt(line.agent_id) : null))
      .filter((value): value is bigint | null => value !== undefined);
    const schemeIds = payload.lines
      .map((line) => (line.profit_share_scheme_id ? parseBigInt(line.profit_share_scheme_id) : null))
      .filter((value): value is bigint | null => value !== undefined);

    if (
      gradeIds.some((id) => id === null) ||
      agentIds.some((id) => id === null) ||
      schemeIds.some((id) => id === null)
    ) {
      return NextResponse.json({ message: "Referensi line tidak valid" }, { status: 400 });
    }

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

    const [buyer, courier, currencies, grades, agents, profitShareSchemes] = 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.grade.findMany({
        where: {
          id: { in: gradeIds as bigint[] },
          status: "ACTIVE"
        }
      }),
      prisma.agent.findMany({
        where: {
          id: {
            in: agentIds.filter((id): id is bigint => Boolean(id))
          }
        }
      }),
      prisma.profitShareScheme.findMany({
        where: {
          id: {
            in: schemeIds.filter((id): id is bigint => Boolean(id))
          },
          status: "ACTIVE"
        }
      })
    ]);

    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 gradeMap = new Map(grades.map((grade) => [grade.id.toString(), grade]));
    const agentMap = new Map(agents.map((agent) => [agent.id.toString(), agent]));
    const schemeMap = new Map(profitShareSchemes.map((scheme) => [scheme.id.toString(), scheme]));

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

    for (const line of payload.lines) {
      const grade = gradeMap.get(line.grade_id);
      if (!grade) {
        return NextResponse.json(
          { message: `Grade ${line.grade_id} tidak ditemukan atau tidak aktif` },
          { status: 404 }
        );
      }

      const agent = line.agent_id ? agentMap.get(line.agent_id) : null;
      if (line.agent_id && !agent) {
        return NextResponse.json(
          { message: `Agent ${line.agent_id} tidak ditemukan atau tidak aktif` },
          { status: 404 }
        );
      }

      const scheme = line.profit_share_scheme_id
        ? schemeMap.get(line.profit_share_scheme_id)
        : null;
      if (line.profit_share_scheme_id && !scheme) {
        return NextResponse.json(
          { message: `Skema bagi hasil ${line.profit_share_scheme_id} tidak ditemukan atau tidak aktif` },
          { status: 404 }
        );
      }

      const nominalBuyer = line.qty_planned * line.selling_price_planned;
      const nominalCompany = nominalBuyer * exchangeRate;
      const plannedSellingPriceCompany = line.selling_price_planned * exchangeRate;
      const shareAgent = scheme?.shareAgent.toNumber() ?? 0;
      const commissionBase = (plannedSellingPriceCompany - line.mal_unit_price) * 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 generateJitSaleNo(saleDate);

    const created = await prisma.$transaction(async (tx) => {
      const sale = await tx.jitSale.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 agent = line.agent_id ? agentMap.get(line.agent_id) ?? null : null;
              const scheme = line.profit_share_scheme_id
                ? schemeMap.get(line.profit_share_scheme_id) ?? null
                : null;
              return {
                gradeId: parseBigInt(line.grade_id)!,
                qtyPlanned: new Prisma.Decimal(roundQty(line.qty_planned)),
                malUnitPrice: new Prisma.Decimal(roundAmount(line.mal_unit_price)),
                sellingPricePlanned: new Prisma.Decimal(roundAmount(line.selling_price_planned)),
                agentId: agent?.id ?? null,
                agentNameSnapshot: agent?.name ?? null,
                profitShareSchemeId: scheme?.id ?? null,
                profitShareSchemeName: scheme?.name ?? null,
                agentSharePercent: scheme
                  ? new Prisma.Decimal(roundAmount(scheme.shareAgent.toNumber()))
                  : null,
                notes: line.notes ?? null
              };
            })
          }
        }
      });

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

    await createAuditTrailSafe({
      userId: auth.user.id,
      action: "JIT_SALE_CREATED",
      entityType: "JIT_SALE",
      entityId: created.id,
      method: request.method,
      pathname: new URL(request.url).pathname,
      statusCode: 201,
      summary: `Penjualan JIT ${created.saleNo} dibuat`,
      metadata: {
        sale_no: created.saleNo,
        buyer_id: created.buyerId.toString(),
        item_count: created.lines.length,
        total_nominal_buyer: created.totalNominalBuyer.toNumber()
      }
    });

    return NextResponse.json(
      {
        data: serializeJitSaleDetail(created)
      },
      { status: 201 }
    );
  } 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 membuat penjualan just in time"
      },
      { status: 500 }
    );
  }
}
