import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
import { NextResponse } from "next/server";

import {
  AGENT_BALANCE_DIRECTIONS,
  AGENT_BALANCE_SOURCES,
  AGENT_BALANCE_TYPES,
  createAgentBalanceMutation
} from "@/features/agents/lib/balance-mutations";
import { serializeAgent } from "@/features/agents/lib/serialize-agent";
import { agentInputSchema } from "@/features/agents/schemas/agent.schema";
import { createAuditTrailSafe } from "@/lib/audit-trail";
import { buildAuditChangeMetadata } from "@/lib/audit-trail-diff";
import { requireApiAccess } from "@/lib/authorization";
import { resolveMasterCode } from "@/lib/master-code";
import { prisma } from "@/lib/prisma";

type RouteContext = { params: Promise<{ id: string }> };

const parseId = (id: string) => {
  try {
    return BigInt(id);
  } catch {
    return null;
  }
};

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

  const parsedId = parseId((await context.params).id);
  if (parsedId === null) return NextResponse.json({ message: "Invalid id" }, { status: 400 });

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

  if (!agent) return NextResponse.json({ message: "Agent not found" }, { status: 404 });
  return NextResponse.json({ data: serializeAgent(agent) });
}

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

  const parsedId = parseId((await context.params).id);
  if (parsedId === null) return NextResponse.json({ message: "Invalid id" }, { status: 400 });

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

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

    if (!existing) return NextResponse.json({ message: "Agent not found" }, { status: 404 });

    const profitShareSchemeId = BigInt(parsed.data.profit_share_scheme_id);
    const bankIds = parsed.data.bank_accounts.map((account) => BigInt(account.bank_id));

    const [scheme, banks] = await Promise.all([
      prisma.profitShareScheme.findFirst({
        where: { id: profitShareSchemeId, status: "ACTIVE" }
      }),
      prisma.bank.findMany({
        where: { id: { in: bankIds }, status: "ACTIVE" }
      })
    ]);

    if (!scheme) {
      return NextResponse.json(
        { message: "Validasi gagal", errors: { profit_share_scheme_id: ["Skema bagi hasil harus dipilih dari master aktif"] } },
        { status: 400 }
      );
    }

    if (banks.length !== bankIds.length) {
      return NextResponse.json(
        { message: "Validasi gagal", errors: { bank_accounts: ["Semua rekening harus memilih bank aktif dari master"] } },
        { status: 400 }
      );
    }

    const resolvedCode = await resolveMasterCode({
      role: auth.user.role,
      prefix: "AGT",
      requestedCode: parsed.data.code,
      existingCode: existing.code,
      countExisting: () => prisma.agent.count({ where: { code: { startsWith: "AGT" } } }),
      exists: async (code) => (await prisma.agent.count({ where: { code, id: { not: parsedId } } })) > 0
    });

    if (!resolvedCode.ok) {
      return NextResponse.json(
        { message: "Validasi gagal", errors: { code: [resolvedCode.message] } },
        { status: 400 }
      );
    }

    const adjustmentDate = new Date();
    const nextProfitShareBalance = parsed.data.profit_share_balance;
    const nextCapitalBalance = parsed.data.capital_balance;
    const previousProfitShareBalance = Number(existing.currentBalance);
    const previousCapitalBalance = Number(existing.capitalBalance);
    const agent = await prisma.$transaction(async (tx) => {
      const updated = await tx.agent.update({
        where: { id: parsedId },
        data: {
          code: resolvedCode.code,
          name: parsed.data.name,
          identityType: parsed.data.identity_type,
          identityNumber: parsed.data.identity_number,
          mobilePhone: parsed.data.mobile_phone || null,
          email: parsed.data.email || null,
          address: parsed.data.address || null,
          notes: parsed.data.notes || null,
          joinDate: new Date(parsed.data.join_date),
          profitShareSchemeId,
          currentBalance: nextProfitShareBalance,
          capitalBalance: nextCapitalBalance,
          bankAccounts: {
            deleteMany: {},
            create: parsed.data.bank_accounts.map((account) => ({
              bankId: BigInt(account.bank_id),
              accountNumber: account.account_number
            }))
          }
        },
        include: {
          profitShareScheme: true,
          bankAccounts: { include: { bank: true } }
        }
      });

      const profitShareDelta = Number((nextProfitShareBalance - previousProfitShareBalance).toFixed(2));
      if (profitShareDelta !== 0) {
        await createAgentBalanceMutation(tx, {
          agentId: updated.id,
          balanceType: AGENT_BALANCE_TYPES.PROFIT_SHARE,
          direction: profitShareDelta > 0 ? AGENT_BALANCE_DIRECTIONS.IN : AGENT_BALANCE_DIRECTIONS.OUT,
          source: AGENT_BALANCE_SOURCES.MANUAL_ADJUSTMENT,
          amount: Math.abs(profitShareDelta),
          balanceAfter: nextProfitShareBalance,
          effectiveDate: adjustmentDate,
          notes: "Penyesuaian manual saldo bagi hasil dari master agent"
        });
      }

      const capitalDelta = Number((nextCapitalBalance - previousCapitalBalance).toFixed(2));
      if (capitalDelta !== 0) {
        await createAgentBalanceMutation(tx, {
          agentId: updated.id,
          balanceType: AGENT_BALANCE_TYPES.CAPITAL,
          direction: capitalDelta > 0 ? AGENT_BALANCE_DIRECTIONS.IN : AGENT_BALANCE_DIRECTIONS.OUT,
          source: AGENT_BALANCE_SOURCES.MANUAL_ADJUSTMENT,
          amount: Math.abs(capitalDelta),
          balanceAfter: nextCapitalBalance,
          effectiveDate: adjustmentDate,
          notes: "Penyesuaian manual saldo modal dari master agent"
        });
      }

      return updated;
    });

    await createAuditTrailSafe({
      userId: auth.user.id,
      action: "AGENT_UPDATED",
      entityType: "AGENT",
      entityId: agent.id,
      method: request.method,
      pathname: new URL(request.url).pathname,
      statusCode: 200,
      summary: `Agent ${agent.code} diubah`,
      metadata: buildAuditChangeMetadata(
        {
          code: existing.code,
          name: existing.name,
          identity_type: existing.identityType,
          identity_number: existing.identityNumber,
          mobile_phone: existing.mobilePhone,
          email: existing.email,
          address: existing.address,
          notes: existing.notes,
          join_date: existing.joinDate.toISOString().slice(0, 10),
          profit_share_scheme_id: existing.profitShareScheme.id.toString(),
          profit_share_balance: Number(existing.currentBalance),
          capital_balance: Number(existing.capitalBalance),
          bank_accounts: existing.bankAccounts.map((account) => ({
            bank_id: account.bank.id.toString(),
            account_number: account.accountNumber
          }))
        },
        {
          code: agent.code,
          name: agent.name,
          identity_type: agent.identityType,
          identity_number: agent.identityNumber,
          mobile_phone: agent.mobilePhone,
          email: agent.email,
          address: agent.address,
          notes: agent.notes,
          join_date: agent.joinDate.toISOString().slice(0, 10),
          profit_share_scheme_id: agent.profitShareScheme.id.toString(),
          profit_share_balance: Number(agent.currentBalance),
          capital_balance: Number(agent.capitalBalance),
          bank_accounts: agent.bankAccounts.map((account) => ({
            bank_id: account.bank.id.toString(),
            account_number: account.accountNumber
          }))
        }
      )
    });

    return NextResponse.json({ data: serializeAgent(agent) });
  } catch (error) {
    if (error instanceof PrismaClientKnownRequestError && error.code === "P2025") {
      return NextResponse.json({ message: "Agent not found" }, { status: 404 });
    }
    if (error instanceof PrismaClientKnownRequestError && error.code === "P2002") {
      return NextResponse.json(
        { message: "Validasi gagal", errors: { identity_number: ["Kode agen atau identitas sudah dipakai"] } },
        { status: 409 }
      );
    }
    throw error;
  }
}

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

  const parsedId = parseId((await context.params).id);
  if (parsedId === null) return NextResponse.json({ message: "Invalid id" }, { status: 400 });

  try {
    const existing = await prisma.agent.findUnique({ where: { id: parsedId } });
    await prisma.agent.delete({ where: { id: parsedId } });

    await createAuditTrailSafe({
      userId: auth.user.id,
      action: "AGENT_DELETED",
      entityType: "AGENT",
      entityId: parsedId,
      method: request.method,
      pathname: new URL(request.url).pathname,
      statusCode: 200,
      summary: `Agent ${existing?.code ?? parsedId.toString()} dihapus`
    });

    return NextResponse.json({ success: true });
  } catch (error) {
    if (error instanceof PrismaClientKnownRequestError && error.code === "P2025") {
      return NextResponse.json({ message: "Agent not found" }, { status: 404 });
    }
    throw error;
  }
}
