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

import {
  AGENT_BALANCE_DIRECTIONS,
  AGENT_BALANCE_SOURCES,
  AGENT_BALANCE_TYPES,
  createAgentBalanceMutation,
  roundBalanceAmount
} from "@/features/agents/lib/balance-mutations";
import { generateFundRequestNo } from "@/features/fund-requests/lib/generate-fund-request-no";
import { serializeFundRequest } from "@/features/fund-requests/lib/serialize-fund-request";
import {
  storeFundRequestProof,
  validateFundRequestProofFile
} from "@/features/fund-requests/lib/store-transfer-proof";
import { getAppSettings } from "@/lib/app-settings";
import { createAuditTrailSafe } from "@/lib/audit-trail";
import { requireApiAccess } from "@/lib/authorization";
import { prisma } from "@/lib/prisma";

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

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

  const items = await prisma.fundRequest.findMany({
    include: {
      agent: true,
      agentBankAccount: true,
      companyBankAccount: true
    },
    orderBy: [{ transferredAt: "desc" }, { id: "desc" }]
  });

  return NextResponse.json({
    data: items.map(serializeFundRequest)
  });
}

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

  try {
    const formData = await request.formData();
    const transferType = String(formData.get("transfer_type") ?? "").trim();
    const referenceNo = String(formData.get("reference_no") ?? "").trim();
    const agentId = parseBigInt(String(formData.get("agent_id") ?? ""));
    const agentBankAccountId = parseBigInt(String(formData.get("agent_bank_account_id") ?? ""));
    const companyBankAccountId = parseBigInt(String(formData.get("company_bank_account_id") ?? ""));
    const amount = Number(formData.get("amount") ?? 0);
    const transferredAtRaw = String(formData.get("transferred_at") ?? "").trim();
    const proofFile = formData.get("transfer_proof_file");

    if (transferType !== "CAPITAL" && transferType !== "PROFIT_SHARE") {
      return NextResponse.json({ message: "Tipe transfer tidak valid" }, { status: 422 });
    }
    if (!referenceNo) {
      return NextResponse.json({ message: "No reff wajib diisi" }, { status: 422 });
    }
    if (agentId === null) {
      return NextResponse.json({ message: "Agen wajib dipilih" }, { status: 422 });
    }
    if (agentBankAccountId === null) {
      return NextResponse.json({ message: "Nomor rekening agen wajib dipilih" }, { status: 422 });
    }
    if (companyBankAccountId === null) {
      return NextResponse.json({ message: "Rekening kantor wajib dipilih" }, { status: 422 });
    }
    if (!Number.isFinite(amount) || amount <= 0) {
      return NextResponse.json({ message: "Nominal transfer harus lebih dari 0" }, { status: 422 });
    }
    if (!transferredAtRaw) {
      return NextResponse.json({ message: "Waktu transfer wajib diisi" }, { status: 422 });
    }

    const transferredAt = new Date(transferredAtRaw);
    if (Number.isNaN(transferredAt.getTime())) {
      return NextResponse.json({ message: "Waktu transfer tidak valid" }, { status: 422 });
    }

    const settings = await getAppSettings();
    const settingRecord = await prisma.appSetting.findUnique({
      where: { singletonKey: "SYSTEM" },
      include: {
        companyBankAccounts: {
          include: { bank: true }
        }
      }
    });
    if (!settingRecord || settingRecord.companyBankAccounts.length === 0) {
      return NextResponse.json(
        { message: "Lengkapi minimal satu rekening kantor di pengaturan sistem terlebih dahulu." },
        { status: 422 }
      );
    }

    const agent = await prisma.agent.findUnique({
      where: { id: agentId },
      include: {
        bankAccounts: {
          include: {
            bank: true
          }
        }
      }
    });

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

    const selectedBankAccount = agent.bankAccounts.find((item) => item.id === agentBankAccountId);
    if (!selectedBankAccount || selectedBankAccount.bank.status !== "ACTIVE") {
      return NextResponse.json({ message: "Rekening agen harus dipilih dari rekening aktif yang valid" }, { status: 422 });
    }
    const selectedCompanyBankAccount = settingRecord.companyBankAccounts.find(
      (item) => item.id === companyBankAccountId
    );
    if (!selectedCompanyBankAccount || selectedCompanyBankAccount.bank.status !== "ACTIVE") {
      return NextResponse.json({ message: "Rekening kantor harus dipilih dari rekening aktif yang valid" }, { status: 422 });
    }

    let proofFileUrl: string | null = null;
    if (proofFile instanceof File && proofFile.size > 0) {
      const validationError = validateFundRequestProofFile(proofFile);
      if (validationError) {
        return NextResponse.json({ message: validationError }, { status: 422 });
      }
      proofFileUrl = await storeFundRequestProof(proofFile);
    }

    const currentProfitShare = Number(agent.currentBalance);
    const currentCapital = Number(agent.capitalBalance);
    const nextProfitShare =
      transferType === "PROFIT_SHARE" ? roundBalanceAmount(currentProfitShare - amount) : currentProfitShare;
    const nextCapital =
      transferType === "CAPITAL" ? roundBalanceAmount(currentCapital + amount) : currentCapital;

    if (nextProfitShare < 0) {
      return NextResponse.json({ message: "Saldo bagi hasil agen tidak mencukupi" }, { status: 422 });
    }

    const requestNo = await generateFundRequestNo(transferredAt);

    const createdId = await prisma.$transaction(async (tx) => {
      const saved = await tx.fundRequest.create({
        data: {
          requestNo,
          referenceNo,
          transferType,
          agentId,
          agentBankAccountId,
          companyBankAccountId,
          agentBankNameSnapshot: selectedBankAccount.bank.name,
          agentAccountNumberSnapshot: selectedBankAccount.accountNumber,
          companyBankNameSnapshot: selectedCompanyBankAccount.bank.name,
          companyAccountNumberSnapshot: selectedCompanyBankAccount.accountNumber,
          amount: new Prisma.Decimal(roundBalanceAmount(amount)),
          currencyCode: settings.currency_code,
          transferredAt,
          transferProofFileUrl: proofFileUrl,
          status: "SUBMITTED",
          createdById: BigInt(auth.user.id)
        },
        include: {
          agent: true,
          agentBankAccount: true,
          companyBankAccount: true
        }
      });

      if (transferType === "PROFIT_SHARE") {
        await tx.agent.update({
          where: { id: agentId },
          data: {
            currentBalance: new Prisma.Decimal(nextProfitShare)
          }
        });

        await createAgentBalanceMutation(tx, {
          agentId,
          balanceType: AGENT_BALANCE_TYPES.PROFIT_SHARE,
          direction: AGENT_BALANCE_DIRECTIONS.OUT,
          source: AGENT_BALANCE_SOURCES.FUND_REQUEST_PROFIT_SHARE,
          amount,
          balanceAfter: nextProfitShare,
          effectiveDate: transferredAt,
          referenceType: "FUND_REQUEST",
          referenceId: saved.id.toString(),
          referenceNo: saved.requestNo,
          notes: `Transfer dana bagi hasil · ${referenceNo}`,
          metadata: {
            transfer_type: transferType,
            reference_no: referenceNo
          }
        });
      } else {
        await tx.agent.update({
          where: { id: agentId },
          data: {
            capitalBalance: new Prisma.Decimal(nextCapital)
          }
        });

        await createAgentBalanceMutation(tx, {
          agentId,
          balanceType: AGENT_BALANCE_TYPES.CAPITAL,
          direction: AGENT_BALANCE_DIRECTIONS.IN,
          source: AGENT_BALANCE_SOURCES.FUND_REQUEST_CAPITAL,
          amount,
          balanceAfter: nextCapital,
          effectiveDate: transferredAt,
          referenceType: "FUND_REQUEST",
          referenceId: saved.id.toString(),
          referenceNo: saved.requestNo,
          notes: `Transfer dana modal · ${referenceNo}`,
          metadata: {
            transfer_type: transferType,
            reference_no: referenceNo
          }
        });
      }

      return saved.id;
    });

    const created = await prisma.fundRequest.findUnique({
      where: { id: createdId },
      include: {
        agent: true,
        agentBankAccount: true,
        companyBankAccount: true
      }
    });

    if (!created) {
      throw new Error("Fund request yang baru dibuat tidak ditemukan.");
    }

    await createAuditTrailSafe({
      userId: auth.user.id,
      action: "FUND_REQUEST_CREATED",
      entityType: "FUND_REQUEST",
      entityId: created.id,
      method: request.method,
      pathname: new URL(request.url).pathname,
      statusCode: 201,
      summary: `Fund request ${created.requestNo} dibuat`
    });

    return NextResponse.json({ data: serializeFundRequest(created) }, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { message: error instanceof Error ? error.message : "Gagal menyimpan fund request" },
      { status: 500 }
    );
  }
}
