import { NextResponse } from "next/server";
import { avisarVenda } from "@/lib/chats/aviso";
import { avisarVendaAprovada } from "@/lib/chats/push";
import { createAdminClient, createClient } from "@/lib/supabase-server";
import { canCompany, getCompanyContext } from "@/lib/company-context";
import { agendarNotaDaVenda } from "@/lib/fiscal/servidor/pdv";
import { ehClienteDoBalcao, entregaDaVendaNoPdv } from "@/lib/digitais/servidor/pdv";
import { checkSaleQuantityLimits } from "@/lib/price-tiers";
import { valorMinimoDoItem } from "@/lib/adicionais";
import { conferirEstoqueDaVenda, mensagemDoErroDeEstoque } from "@/lib/estoque-da-venda";
import {
  getLiquidationCompatibilityError,
  normalizeLiquidationMethod,
} from "@/lib/liquidation-accounts";

export const runtime = "nodejs";

/*
  O fechamento da venda do PDV.

  Tudo o que pode recusar a venda roda **antes** de qualquer gravação —
  itens, vendedor, quantidades, contas, caixa aberto, cobrança Pix. Por isso a
  rota aceita `simular: true`: o balcão faz o ensaio completo antes de cobrar o
  Pix do cliente, e nenhuma regra fica para ser descoberta depois que o
  dinheiro já entrou.
*/

type IncomingItem = {
  product_id: string | null;
  title: string;
  /* Peças, sempre — mesmo no item por m². */
  quantity: number;
  unit_type: "unit" | "square_meter";
  width_m: number | null;
  height_m: number | null;
  unit_price: number;
  note?: string | null;
  is_free_item?: boolean;
  selected_options?: Record<string, unknown> | null;
  /* Valor mínimo do item (migration 194): o carrinho manda; só vale se for o
     mínimo do cadastro do produto (conferido abaixo). */
  valor_minimo_aplicado?: number | null;
  valor_sem_minimo?: number | null;
};

type IncomingPayment = {
  method: string;
  amount: number;
  bank_account_id?: string | null;
  /* Pix cobrado pelo Mercado Pago no balcão: a venda só fecha se a cobrança
     estiver paga de verdade. */
  pix_charge_id?: string | null;
};

type CobrancaPixDoBalcao = {
  id: string;
  company_id: string;
  status: string;
  amount: number;
  provider_payment_id: string | null;
  order_id: string | null;
};

type LiquidationAccountRow = {
  id: string;
  active: boolean | null;
  type: string | null;
  name: string | null;
  bank_name: string | null;
};

type IncomingAllocation = {
  seller_user_id: string;
  allocation_percent: number;
  is_primary?: boolean;
};

type ProductSaleQuantityRow = {
  id: string;
  unit_type?: string | null;
  price_on_request?: boolean | null;
  valor_minimo_item?: number | string | null;
  valor_minimo_pdv?: boolean | null;
  min_sale_qty_enabled?: boolean | null;
  min_sale_qty?: number | null;
  max_sale_qty_enabled?: boolean | null;
  max_sale_qty?: number | null;
  allowed_sale_qty_enabled?: boolean | null;
  allowed_sale_quantities?: number[] | null;
};

type PagamentoResolvido = IncomingPayment & {
  mapped_method: NonNullable<ReturnType<typeof normalizeLiquidationMethod>>;
  resolved_bank_account_id: string;
};

function isRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}

function isUuid(value: string) {
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
}

function normalizeAllocation(raw: unknown): IncomingAllocation | null {
  if (!isRecord(raw)) return null;
  const sellerUserId = typeof raw.seller_user_id === "string" ? raw.seller_user_id.trim() : "";
  const allocationPercent =
    typeof raw.allocation_percent === "number" && Number.isFinite(raw.allocation_percent)
      ? Number(raw.allocation_percent.toFixed(4))
      : 0;

  if (!sellerUserId || !isUuid(sellerUserId) || allocationPercent <= 0) return null;

  return {
    seller_user_id: sellerUserId,
    allocation_percent: allocationPercent,
    is_primary: raw.is_primary === true,
  };
}

function toNumber(value: unknown, fallback = 0) {
  if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
  return value;
}

function normalizeItem(raw: unknown): IncomingItem | null {
  if (!isRecord(raw)) return null;
  const productIdRaw = typeof raw.product_id === "string" ? raw.product_id.trim() : "";
  const title = typeof raw.title === "string" ? raw.title.trim() : "";
  const quantity = toNumber(raw.quantity, 0);
  const unitPrice = Math.max(0, toNumber(raw.unit_price, 0));
  /* Item por m²: o carrinho manda peças e medida; a área é conta do servidor. */
  const isSquareMeter = raw.unit_type === "square_meter";
  const widthM = isSquareMeter ? Math.max(0, toNumber(raw.width_m, 0)) : 0;
  const heightM = isSquareMeter ? Math.max(0, toNumber(raw.height_m, 0)) : 0;
  const note = typeof raw.note === "string" ? raw.note.trim() : "";
  const isFreeItem = raw.is_free_item === true;
  const selectedOptions = isRecord(raw.selected_options) ? raw.selected_options : null;
  const minimo = toNumber(raw.valor_minimo_aplicado, 0);
  const semMinimo = toNumber(raw.valor_sem_minimo, -1);
  /* O mínimo só existe em item de produto do cadastro (nunca no item avulso). */
  const comMinimo = !isFreeItem && minimo > 0 && Boolean(productIdRaw && isUuid(productIdRaw));
  if (!title || quantity <= 0) return null;
  return {
    product_id: productIdRaw && isUuid(productIdRaw) ? productIdRaw : null,
    title,
    quantity,
    unit_type: isSquareMeter && widthM > 0 && heightM > 0 ? "square_meter" : "unit",
    width_m: isSquareMeter && widthM > 0 && heightM > 0 ? widthM : null,
    height_m: isSquareMeter && widthM > 0 && heightM > 0 ? heightM : null,
    unit_price: unitPrice,
    note: note || null,
    is_free_item: isFreeItem,
    selected_options: selectedOptions,
    valor_minimo_aplicado: comMinimo ? Math.round(minimo * 100) / 100 : null,
    valor_sem_minimo: comMinimo && semMinimo >= 0 ? Math.round(semMinimo * 100) / 100 : null,
  };
}

/* O total da linha: o mínimo do item, quando pegou; senão quantidade × preço. */
function totalDoItem(item: IncomingItem) {
  if (item.valor_minimo_aplicado != null && item.valor_minimo_aplicado > 0) return item.valor_minimo_aplicado;
  return effectiveQuantity(item) * item.unit_price;
}

function normalizePayment(raw: unknown): IncomingPayment | null {
  if (!isRecord(raw)) return null;
  const method = typeof raw.method === "string" ? raw.method.trim().toLowerCase() : "";
  const amount = Math.max(0, toNumber(raw.amount, 0));
  const bankAccountId =
    typeof raw.bank_account_id === "string" && raw.bank_account_id.trim() ? raw.bank_account_id.trim() : null;
  const pixChargeId =
    typeof raw.pix_charge_id === "string" && isUuid(raw.pix_charge_id.trim()) ? raw.pix_charge_id.trim() : null;
  if (!method || amount <= 0) return null;
  return { method, amount, bank_account_id: bankAccountId, pix_charge_id: pixChargeId };
}

/* A quantidade que multiplica o preço: peças no item por unidade, peças × área
   no item por m² (o preço é por m²). */
function effectiveQuantity(item: Pick<IncomingItem, "quantity" | "unit_type" | "width_m" | "height_m">) {
  const pecas = item.quantity > 0 ? item.quantity : 0;
  if (item.unit_type === "square_meter" && item.width_m && item.height_m) {
    return Math.round(pecas * item.width_m * item.height_m * 10000) / 10000;
  }
  return pecas;
}

function mapPaymentMethod(method: string) {
  if (method === "pix") return "pix";
  if (method === "cash" || method === "dinheiro") return "cash";
  if (method === "debit_card" || method === "debit") return "debit_card";
  if (method === "credit_card" || method === "card" || method === "cartao") return "credit_card";
  if (method === "transfer" || method === "transferencia") return "transfer";
  return null;
}

async function getQuoteNumberStartFrom(
  supabase: Awaited<ReturnType<typeof createClient>>,
  tenantId: string
) {
  const { data, error } = await supabase
    .from("profiles")
    .select("quote_number_start_from")
    .eq("id", tenantId)
    .maybeSingle();

  if (error) return 1;
  const rawValue = (data as { quote_number_start_from?: unknown } | null)?.quote_number_start_from;
  const parsed = Number(rawValue);
  if (!Number.isFinite(parsed)) return 1;
  return Math.max(1, Math.floor(parsed));
}

async function getNextQuoteNumber(supabase: Awaited<ReturnType<typeof createClient>>, tenantId: string) {
  const { data, error } = await supabase
    .from("quotes")
    .select("number")
    .eq("user_id", tenantId)
    .order("number", { ascending: false })
    .limit(1);

  if (error) throw error;
  const currentMax = data && data.length > 0 ? Number((data[0] as { number?: unknown } | null)?.number ?? 0) : 0;
  return currentMax + 1;
}

async function ensurePdvClient(supabase: Awaited<ReturnType<typeof createClient>>, tenantId: string) {
  const PDV_CLIENT_NAME = "Cliente balcão";
  const PDV_CLIENT_COMPANY = "PDV";
  const { data: existing, error: existingError } = await supabase
    .from("clients")
    .select("id,user_id,name,company_name")
    .eq("user_id", tenantId)
    .eq("name", PDV_CLIENT_NAME)
    .eq("company_name", PDV_CLIENT_COMPANY)
    .order("created_at", { ascending: true })
    .limit(1)
    .maybeSingle();

  if (existingError) throw existingError;
  if (existing) return existing as { id: string; user_id: string; name: string | null; company_name: string | null };

  let payload: Record<string, unknown> = {
    user_id: tenantId,
    name: PDV_CLIENT_NAME,
    company_name: PDV_CLIENT_COMPANY,
    type: "pf",
    active: true,
    notes: "Cliente técnico do PDV.",
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString(),
  };

  for (let attempt = 0; attempt < 3; attempt++) {
    const { data, error } = await supabase
      .from("clients")
      .insert([payload])
      .select("id,user_id,name,company_name")
      .single();

    if (!error) {
      return data as { id: string; user_id: string; name: string | null; company_name: string | null };
    }

    const message = (error.message || "").toLowerCase();
    if (message.includes("column") && message.includes("active") && "active" in payload) {
      const next = { ...payload };
      delete next.active;
      payload = next;
      continue;
    }
    if (message.includes("column") && message.includes("notes") && "notes" in payload) {
      const next = { ...payload };
      delete next.notes;
      payload = next;
      continue;
    }
    if (error.code === "23505") {
      const retry = await supabase
        .from("clients")
        .select("id,user_id,name,company_name")
        .eq("user_id", tenantId)
        .eq("name", PDV_CLIENT_NAME)
        .eq("company_name", PDV_CLIENT_COMPANY)
        .order("created_at", { ascending: true })
        .limit(1)
        .maybeSingle();
      if (retry.error) throw retry.error;
      if (retry.data) {
        return retry.data as { id: string; user_id: string; name: string | null; company_name: string | null };
      }
    }
    throw error;
  }

  throw new Error("Não foi possível preparar cliente padrão do PDV.");
}

/* Produto digital: a entrega sai agora e o balcão mostra o QR code do link (venda paga ou grátis). */
async function entregarDigitalDoBalcao(params: { lojaId: string; pedidoId: string; clienteId: string; quem: string; request: Request }) {
  try {
    return await entregaDaVendaNoPdv(createAdminClient(), params);
  } catch (erro) {
    console.error("[digitais] entrega da venda no PDV", params.pedidoId, erro instanceof Error ? erro.message : erro);
    return null;
  }
}

export async function POST(req: Request) {
  const supabase = await createClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();

  if (!user) return NextResponse.json({ error: "Não autenticado." }, { status: 401 });

  const ctx = await getCompanyContext(supabase, user);
  if (!canCompany(ctx, user.id, "quotes.write") || !canCompany(ctx, user.id, "orders.write") || !canCompany(ctx, user.id, "financial.write")) {
    return NextResponse.json({ error: "Sem permissão para concluir venda no PDV." }, { status: 403 });
  }

  const body = (await req.json().catch(() => null)) as Record<string, unknown> | null;
  /* O ensaio: todas as regras, nenhuma gravação. */
  const simular = body?.simular === true;
  const incomingItems = Array.isArray(body?.items) ? body.items : [];
  const incomingPayments = Array.isArray(body?.payments) ? body.payments : [];

  const items = incomingItems.map(normalizeItem).filter(Boolean) as IncomingItem[];
  if (items.length === 0) {
    return NextResponse.json({ error: "Adicione ao menos 1 item para finalizar a venda." }, { status: 400 });
  }

  const discount = Math.max(0, toNumber(body?.discount, 0));
  const subtotal = items.reduce((sum, item) => sum + totalDoItem(item), 0);
  const total = Math.max(0, subtotal - discount);
  const cashReceivedRaw = Math.max(0, toNumber(body?.cash_received, 0));

  const payments = incomingPayments.map(normalizePayment).filter(Boolean) as IncomingPayment[];
  const paidTotal = payments.reduce((sum, payment) => sum + payment.amount, 0);
  const cashTotal = payments
    .filter((payment) => {
      const mapped = mapPaymentMethod(payment.method);
      return mapped === "cash";
    })
    .reduce((sum, payment) => sum + payment.amount, 0);

  if (total > 0 && paidTotal + 0.01 < total) {
    return NextResponse.json({ error: "Pagamento insuficiente para concluir a venda." }, { status: 400 });
  }

  if (paidTotal - total > 0.01) {
    return NextResponse.json({ error: "Pagamento acima do total. Ajuste os valores das formas de pagamento." }, { status: 400 });
  }

  if (cashTotal <= 0.0001 && cashReceivedRaw > 0.009) {
    return NextResponse.json({ error: "Valor recebido só pode ser informado quando houver pagamento em dinheiro." }, { status: 400 });
  }

  if (cashTotal > 0.0001 && cashReceivedRaw > 0.009 && cashReceivedRaw + 0.01 < cashTotal) {
    return NextResponse.json({ error: "Valor recebido em dinheiro não pode ser menor que o valor em dinheiro da venda." }, { status: 400 });
  }

  const effectiveCashReceived = cashTotal > 0.0001 ? Math.max(cashReceivedRaw, cashTotal) : 0;
  const change = cashTotal > 0.0001 ? Math.max(0, effectiveCashReceived - cashTotal) : 0;

  const tenantIds = ctx.tenantIds?.length ? ctx.tenantIds : [user.id];
  const selectedClientId = typeof body?.client_id === "string" ? body.client_id.trim() : "";
  const requestedBankAccountId = typeof body?.bank_account_id === "string" ? body.bank_account_id.trim() : "";
  const sellerMode = body?.seller_mode === "split" ? "split" : "single";
  const requestedPrimarySellerUserId =
    typeof body?.primary_seller_user_id === "string" && isUuid(body.primary_seller_user_id)
      ? body.primary_seller_user_id
      : "";
  const requestedAllocations = Array.isArray(body?.allocations)
    ? body.allocations.map(normalizeAllocation).filter(Boolean) as IncomingAllocation[]
    : [];

  let client: { id: string; user_id: string; name: string | null; company_name: string | null; is_deleted?: boolean | null } | null = null;

  if (selectedClientId) {
    const { data, error } = await supabase
      .from("clients")
      .select("id,user_id,name,company_name,is_deleted")
      .eq("id", selectedClientId)
      .in("user_id", tenantIds)
      .maybeSingle();
    if (error) return NextResponse.json({ error: error.message }, { status: 400 });
    if (!data) return NextResponse.json({ error: "Cliente selecionado não encontrado." }, { status: 404 });
    if ((data as { is_deleted?: boolean | null }).is_deleted) {
      return NextResponse.json({ error: "Cliente selecionado está excluído." }, { status: 400 });
    }
    client = data as { id: string; user_id: string; name: string | null; company_name: string | null; is_deleted?: boolean | null };
  } else if (!simular) {
    /* No ensaio o cliente técnico não é criado: não se grava nada. */
    const quickTenantId = ctx.ownerId || user.id;
    try {
      client = await ensurePdvClient(supabase, quickTenantId);
    } catch (error) {
      const message = error instanceof Error ? error.message : "Não foi possível preparar o cliente padrão do PDV.";
      return NextResponse.json({ error: message }, { status: 400 });
    }
  }

  if (!simular && (!client?.id || !client.user_id)) {
    return NextResponse.json({ error: "Cliente inválido para concluir a venda." }, { status: 400 });
  }

  /* De quem é a venda: do dono da conta, sempre — com ou sem cliente escolhido. */
  const tenantId = client?.user_id || ctx.ownerId || user.id;

  const sellerIds = Array.from(
    new Set(
      [
        requestedPrimarySellerUserId,
        ...requestedAllocations.map((allocation) => allocation.seller_user_id),
      ].filter(Boolean)
    )
  );

  if (sellerMode === "single" && !requestedPrimarySellerUserId) {
    return NextResponse.json({ error: "Selecione o vendedor principal da venda." }, { status: 400 });
  }

  if (sellerMode === "split" && requestedAllocations.length < 2) {
    return NextResponse.json({ error: "Informe ao menos dois vendedores para o rateio da venda." }, { status: 400 });
  }

  const duplicateSellerId = requestedAllocations.find(
    (allocation, index) =>
      requestedAllocations.findIndex((candidate) => candidate.seller_user_id === allocation.seller_user_id) !== index
  )?.seller_user_id;
  if (duplicateSellerId) {
    return NextResponse.json({ error: "O mesmo vendedor não pode aparecer mais de uma vez no rateio." }, { status: 400 });
  }

  const allocationTotal = requestedAllocations.reduce((sum, allocation) => sum + allocation.allocation_percent, 0);
  if (sellerMode === "split" && Math.abs(allocationTotal - 100) > 0.01) {
    return NextResponse.json({ error: "O rateio deve totalizar exatamente 100%." }, { status: 400 });
  }

  if (sellerIds.length === 0) {
    return NextResponse.json({ error: "Nenhum vendedor válido foi informado." }, { status: 400 });
  }

  const { data: sellerMemberships, error: sellerMembershipsError } = await supabase
    .from("company_users")
    .select("user_id,active")
    .eq("company_id", ctx.companyId)
    .in("user_id", sellerIds);

  if (sellerMembershipsError) {
    return NextResponse.json({ error: "Falha ao validar vendedores da empresa." }, { status: 400 });
  }

  const allowedSellerIds = new Set(
    ((sellerMemberships as Array<{ user_id: string; active: boolean }> | null) || [])
      .filter((membership) => membership.active)
      .map((membership) => membership.user_id)
  );
  if (ctx.ownerId) {
    allowedSellerIds.add(ctx.ownerId);
  }

  const invalidSellerId = sellerIds.find((sellerId) => !allowedSellerIds.has(sellerId));
  if (invalidSellerId) {
    return NextResponse.json({ error: "Há vendedor inválido ou inativo na venda." }, { status: 400 });
  }

  const salesAssignment =
    sellerMode === "split"
      ? {
          sellerMode,
          primarySellerUserId:
            requestedAllocations.find((allocation) => allocation.is_primary)?.seller_user_id ||
            requestedAllocations[0]?.seller_user_id ||
            "",
          allocations: requestedAllocations.map((allocation, index) => ({
            seller_user_id: allocation.seller_user_id,
            allocation_percent: Number(allocation.allocation_percent.toFixed(4)),
            is_primary:
              allocation.is_primary === true ||
              (!requestedAllocations.some((candidate) => candidate.is_primary) && index === 0),
          })),
        }
      : {
          sellerMode,
          primarySellerUserId: requestedPrimarySellerUserId,
          allocations: [
            {
              seller_user_id: requestedPrimarySellerUserId,
              allocation_percent: 100,
              is_primary: true,
            },
          ],
        };

  let temDigital = false;
  const productIds = Array.from(new Set(items.map((item) => item.product_id).filter((id): id is string => Boolean(id))));
  if (productIds.length > 0) {
    const { data: productsForLimits, error: productsForLimitsError } = await supabase
      .from("products")
      .select("id,min_sale_qty_enabled,min_sale_qty,max_sale_qty_enabled,max_sale_qty,allowed_sale_qty_enabled,allowed_sale_quantities,product_kind,unit_type,price_on_request,valor_minimo_item,valor_minimo_pdv")
      .eq("user_id", tenantId)
      .in("id", productIds);

    if (productsForLimitsError) {
      return NextResponse.json({ error: productsForLimitsError.message }, { status: 400 });
    }

    const productLimitsById = new Map(
      ((productsForLimits as ProductSaleQuantityRow[] | null) || []).map((product) => [product.id, product])
    );

    for (const item of items) {
      if (!item.product_id) continue;
      const product = productLimitsById.get(item.product_id);
      if (!product) {
        return NextResponse.json({ error: `Produto do item "${item.title}" não foi encontrado.` }, { status: 400 });
      }

      const quantityCheck = checkSaleQuantityLimits({
        min_sale_qty_enabled: product.min_sale_qty_enabled,
        min_sale_qty: product.min_sale_qty,
        max_sale_qty_enabled: product.max_sale_qty_enabled,
        max_sale_qty: product.max_sale_qty,
        allowed_sale_qty_enabled: product.allowed_sale_qty_enabled,
        allowed_sale_quantities: product.allowed_sale_quantities,
        effectiveQuantity: effectiveQuantity(item),
      });

      if (quantityCheck.isBelowMin) {
        return NextResponse.json({ error: `Item "${item.title}" está abaixo da quantidade mínima.` }, { status: 400 });
      }
      if (quantityCheck.isAboveMax) {
        return NextResponse.json({ error: `Item "${item.title}" está acima da quantidade máxima.` }, { status: 400 });
      }
      if (quantityCheck.isNotAllowedValue) {
        return NextResponse.json({ error: `Item "${item.title}" possui quantidade não permitida.` }, { status: 400 });
      }

      /* O valor mínimo do item que o carrinho mandou precisa ser o do
         cadastro, e a conta automática precisa dar menos que ele. */
      if (item.valor_minimo_aplicado != null) {
        const doCadastro = valorMinimoDoItem(product, "pdv");
        const automatico = Math.round(effectiveQuantity(item) * item.unit_price * 100) / 100;
        if (doCadastro === null || Math.abs(doCadastro - item.valor_minimo_aplicado) > 0.005 || automatico >= doCadastro) {
          return NextResponse.json(
            { error: `O valor mínimo de "${item.title}" mudou no cadastro. Tire o item do cupom e coloque de novo.` },
            { status: 400 }
          );
        }
      }
    }

    /* Produto digital: o acesso é de uma pessoa. A venda pede o cliente de verdade (o do balcão não serve). */
    temDigital = ((productsForLimits as Array<{ product_kind?: string | null }> | null) || []).some((p) => p.product_kind === "digital");
    if (temDigital && (!selectedClientId || ehClienteDoBalcao(client))) {
      return NextResponse.json(
        { error: "Produto digital: escolha o cliente da venda. O acesso vai para ele, no e-mail e pelo QR code na tela." },
        { status: 400 }
      );
    }
  }

  /*
    Pix do Mercado Pago: quem diz que está pago é o registro da cobrança, não
    o navegador. Uma cobrança pode aparecer em mais de uma parte do pagamento
    (o balcão pode ter dividido o Pix na tela); o que precisa bater é a
    **soma** das partes que apontam para ela. Cobrança de outra empresa,
    pendente, de outro valor ou já usada numa venda derruba o checkout antes
    de qualquer gravação.
  */
  const cobrancasPix = new Map<string, CobrancaPixDoBalcao>();
  const idsDeCobranca = Array.from(
    new Set(payments.map((payment) => payment.pix_charge_id).filter((id): id is string => Boolean(id)))
  );
  if (idsDeCobranca.length > 0) {
    const admin = createAdminClient();
    const { data: linhas, error: erroDasCobrancas } = await admin
      .from("pdv_pix_charges")
      .select("id,company_id,status,amount,provider_payment_id,order_id")
      .in("id", idsDeCobranca)
      .eq("company_id", ctx.companyId);
    if (erroDasCobrancas) {
      return NextResponse.json({ error: "Falha ao conferir a cobrança Pix.", details: erroDasCobrancas.message }, { status: 500 });
    }
    for (const linha of (linhas as CobrancaPixDoBalcao[] | null) ?? []) cobrancasPix.set(linha.id, linha);

    const somaPorCobranca = new Map<string, number>();
    for (const payment of payments) {
      if (!payment.pix_charge_id) continue;
      if (mapPaymentMethod(payment.method) !== "pix") {
        return NextResponse.json({ error: "A cobrança do Mercado Pago só vale para pagamento em Pix." }, { status: 400 });
      }
      somaPorCobranca.set(payment.pix_charge_id, (somaPorCobranca.get(payment.pix_charge_id) || 0) + payment.amount);
    }

    for (const id of idsDeCobranca) {
      const cobranca = cobrancasPix.get(id);
      if (!cobranca) {
        return NextResponse.json({ error: "Cobrança Pix não encontrada para esta empresa." }, { status: 400 });
      }
      if (cobranca.status !== "approved") {
        return NextResponse.json({ error: "O Pix ainda não foi confirmado pelo Mercado Pago." }, { status: 400 });
      }
      if (cobranca.order_id) {
        return NextResponse.json({ error: "Esta cobrança Pix já foi usada em outra venda." }, { status: 400 });
      }
      const soma = somaPorCobranca.get(id) || 0;
      if (Math.abs(Number(cobranca.amount || 0) - soma) > 0.01) {
        return NextResponse.json(
          {
            error: `O Pix pago foi de R$ ${Number(cobranca.amount || 0).toFixed(2).replace(".", ",")}, mas a venda informa R$ ${soma
              .toFixed(2)
              .replace(".", ",")} em Pix.`,
          },
          { status: 400 }
        );
      }
    }
  }

  /*
    As contas onde o dinheiro cai e o caixa aberto — conferidos aqui, antes de
    criar orçamento ou pedido. Antes isto rodava depois do pedido existir, e
    uma conta errada deixava um pedido órfão para trás.
  */
  const resolvedPayments: PagamentoResolvido[] = [];
  let activeCashSessionId = "";

  if (total > 0) {
    const paymentAccountIds = Array.from(
      new Set(
        payments
          .map((payment) => payment.bank_account_id?.trim() || requestedBankAccountId)
          .filter((value): value is string => Boolean(value))
      )
    );
    const accountMap = new Map<string, LiquidationAccountRow>();

    if (paymentAccountIds.length > 0) {
      const { data: bankAccounts, error: bankAccountsError } = await supabase
        .from("bank_accounts")
        .select("id,active,type,name,bank_name")
        .eq("company_id", ctx.companyId)
        .in("id", paymentAccountIds);

      if (bankAccountsError) {
        return NextResponse.json(
          { error: "Falha ao validar as contas de liquidação selecionadas.", details: bankAccountsError.message },
          { status: 500 }
        );
      }

      for (const account of (bankAccounts as LiquidationAccountRow[] | null) ?? []) {
        accountMap.set(account.id, account);
      }
    }

    const cashAccountIds = new Set<string>();

    for (const payment of payments) {
      const mappedMethod = normalizeLiquidationMethod(payment.method);
      if (!mappedMethod) {
        return NextResponse.json({ error: `Método de pagamento inválido: ${payment.method}.` }, { status: 400 });
      }

      const resolvedBankAccountId = payment.bank_account_id?.trim() || requestedBankAccountId;
      if (!resolvedBankAccountId) {
        return NextResponse.json(
          {
            error:
              mappedMethod === "cash"
                ? "Selecione o caixa físico para registrar os pagamentos em dinheiro do PDV."
                : "Selecione a conta bancária para registrar os pagamentos eletrônicos do PDV.",
          },
          { status: 400 }
        );
      }

      const selectedAccount = accountMap.get(resolvedBankAccountId);
      if (!selectedAccount) {
        return NextResponse.json(
          {
            error:
              mappedMethod === "cash"
                ? "Caixa físico selecionado não encontrado para esta empresa."
                : "Conta bancária selecionada não encontrada para esta empresa.",
          },
          { status: 400 }
        );
      }

      const compatibilityError = getLiquidationCompatibilityError(mappedMethod, selectedAccount);
      if (compatibilityError) {
        return NextResponse.json({ error: compatibilityError }, { status: 400 });
      }

      resolvedPayments.push({
        ...payment,
        mapped_method: mappedMethod,
        resolved_bank_account_id: resolvedBankAccountId,
      });

      if (mappedMethod === "cash") {
        cashAccountIds.add(resolvedBankAccountId);
      }
    }

    if (resolvedPayments.length === 0) {
      return NextResponse.json({ error: "Informe ao menos um pagamento para concluir a venda." }, { status: 400 });
    }

    if (cashAccountIds.size > 1) {
      return NextResponse.json({ error: "A venda do PDV pode usar apenas um caixa físico por operação." }, { status: 400 });
    }

    const cashAccountId = Array.from(cashAccountIds)[0] ?? "";
    if (cashAccountId) {
      const { data: activeSession, error: activeSessionError } = await supabase
        .from("pdv_cash_sessions")
        .select("id,status")
        .eq("company_id", ctx.companyId)
        .eq("bank_account_id", cashAccountId)
        .eq("status", "open")
        .order("opened_at", { ascending: false })
        .limit(1)
        .maybeSingle();
      if (activeSessionError) {
        return NextResponse.json({ error: "Falha ao validar sessão de caixa ativa.", details: activeSessionError.message }, { status: 500 });
      }
      if (!activeSession?.id) {
        return NextResponse.json({ error: "Abra o caixa físico selecionado antes de finalizar a venda em dinheiro no PDV." }, { status: 400 });
      }
      activeCashSessionId = activeSession.id;
    }
  }

  /*
    O estoque do cupom inteiro, pela MESMA conta da loja e do orçamento
    (estoque_conferir_itens: soma por célula no estoque v2, regra de sempre
    fora dele; quem não controla ou permite negativo nunca falta). Vale no
    ensaio do Pix também: antes, o Pix era pago e a venda recusada pelo
    banco depois. Com o Pix JÁ pago (cobrança aprovada) a venda não pode mais
    ser recusada: o pedido nasce como "venda já paga" (reposição pendente).
  */
  const pixJaPago = idsDeCobranca.length > 0;
  if (!pixJaPago) {
    const estoque = await conferirEstoqueDaVenda(supabase, ctx.ownerId || user.id, items);
    if (estoque.frase) {
      return NextResponse.json({ error: estoque.frase, code: "estoque_insuficiente" }, { status: 409 });
    }
  }

  /* O ensaio termina aqui: passou em tudo, nada foi gravado. */
  if (simular) {
    return NextResponse.json({ ok: true, simulado: true, total, paidTotal, change });
  }

  if (!client?.id || !client.user_id) {
    return NextResponse.json({ error: "Cliente inválido para concluir a venda." }, { status: 400 });
  }

  const quoteNumberFloor = await getQuoteNumberStartFrom(supabase, client.user_id);
  let quoteId = "";
  let quoteNumber = 0;
  let createQuoteError: unknown = null;

  for (let attempt = 0; attempt < 4; attempt++) {
    try {
      const sequentialNumber = await getNextQuoteNumber(supabase, client.user_id);
      const number = Math.max(sequentialNumber, quoteNumberFloor);
      const now = new Date().toISOString();
      const title = `Venda PDV ${new Date().toLocaleString("pt-BR")}`;
      const notes = typeof body?.notes === "string" && body.notes.trim() ? body.notes.trim() : "Venda originada no PDV.";
      const internalNotes = "origem:pdv";

      const { data: createdQuote, error: quoteError } = await supabase
        .from("quotes")
        .insert([
          {
            user_id: client.user_id,
            client_id: client.id,
            number,
            status: "draft",
            title,
            description: null,
            currency: "BRL",
            subtotal,
            discount,
            shipping: 0,
            shipping_details: null,
            list_price_mode: false,
            seller_mode: salesAssignment.sellerMode,
            primary_seller_user_id: salesAssignment.primarySellerUserId || null,
            total,
            expires_at: null,
            delivery_date: null,
            notes,
            internal_notes: internalNotes,
            /* Pix já pago: estoque e quantidade não podem mais recusar o
               pedido (migration 208); a falta vira reposição pendente. */
            ...(pixJaPago ? { venda_ja_paga: true } : {}),
            created_at: now,
            updated_at: now,
          },
        ])
        .select("id,number")
        .single();

      if (quoteError) {
        if (quoteError.code === "23505") {
          createQuoteError = quoteError;
          continue;
        }
        throw quoteError;
      }

      quoteId = (createdQuote as { id: string }).id;
      quoteNumber = Number((createdQuote as { number: number }).number);
      break;
    } catch (error) {
      createQuoteError = error;
    }
  }

  if (!quoteId) {
    const message = createQuoteError instanceof Error ? createQuoteError.message : "Falha ao criar orçamento técnico da venda.";
    return NextResponse.json({ error: message }, { status: 400 });
  }

  const quoteItemsPayload = items.map((item, index) => ({
    quote_id: quoteId,
    position: index + 1,
    product_id: item.product_id,
    unit_type: item.unit_type,
    width_m: item.width_m,
    height_m: item.height_m,
    length_m: null,
    title: item.title,
    description: item.note || null,
    quantity: item.quantity,
    unit_price: item.unit_price,
    unit_cost: 0,
    line_total: totalDoItem(item),
    selected_options: item.selected_options || null,
    valor_minimo_aplicado: item.valor_minimo_aplicado ?? null,
    valor_sem_minimo: item.valor_minimo_aplicado != null ? item.valor_sem_minimo ?? null : null,
  }));

  const { error: quoteItemsError } = await supabase.from("quote_items").insert(quoteItemsPayload);
  if (quoteItemsError) {
    await supabase.from("quotes").delete().eq("id", quoteId);
    return NextResponse.json({ error: quoteItemsError.message }, { status: 400 });
  }

  const allocationsPayload = salesAssignment.allocations.map((allocation) => ({
    company_id: ctx.companyId,
    quote_id: quoteId,
    seller_user_id: allocation.seller_user_id,
    allocation_percent: allocation.allocation_percent,
    allocation_basis: "revenue",
    is_primary: allocation.is_primary,
    created_by: user.id,
  }));

  const { error: allocationsError } = await supabase.from("sale_commission_allocations").insert(allocationsPayload);
  if (allocationsError) {
    await supabase.from("quotes").delete().eq("id", quoteId);
    return NextResponse.json({ error: allocationsError.message }, { status: 400 });
  }

  const { data: approveResult, error: approveError } = await supabase.rpc("approve_quote_and_generate_order", { p_quote_id: quoteId });
  if (approveError) {
    /* O pedido não nasceu (a aprovação desfaz tudo dela): o orçamento técnico
       da venda também sai, para não sobrar um "Venda PDV" a cada tentativa. */
    await supabase.from("sale_commission_allocations").delete().eq("quote_id", quoteId);
    await supabase.from("quote_items").delete().eq("quote_id", quoteId);
    await supabase.from("quotes").delete().eq("id", quoteId);
    return NextResponse.json({ error: mensagemDoErroDeEstoque(approveError, "Não foi possível gerar o pedido da venda.") }, { status: 400 });
  }

  const orderId = (approveResult as { order_id?: unknown } | null)?.order_id;
  const orderNumber = Number((approveResult as { order_number?: unknown } | null)?.order_number ?? 0) || null;

  if (typeof orderId !== "string" || !orderId) {
    return NextResponse.json({ error: "Pedido não foi gerado corretamente.", quoteId }, { status: 400 });
  }

  const { error: finalizeOrderError } = await supabase
    .from("orders")
    .update({
      status: "delivered",
      updated_at: new Date().toISOString(),
      internal_notes: "Venda rápida PDV finalizada automaticamente.",
    })
    .eq("id", orderId)
    .eq("user_id", client.user_id);

  if (finalizeOrderError) {
    return NextResponse.json(
      { error: "Pedido foi criado, mas falhou ao marcar como finalizado PDV.", details: finalizeOrderError.message, orderId, quoteId },
      { status: 500 }
    );
  }

  await supabase.from("order_history").insert({
    order_id: orderId,
    user_id: user.id,
    status_from: "pending_production",
    status_to: "delivered",
    notes: "PDV: pedido finalizado automaticamente sem etapa de produção.",
  });

  if (total <= 0) {
    await supabase
      .from("orders")
      .update({ paid_amount: 0, financial_status: "paid" })
      .eq("id", orderId)
      .eq("user_id", client.user_id);
    return NextResponse.json({
      ok: true,
      entregaDigital: temDigital
        ? await entregarDigitalDoBalcao({ lojaId: client.user_id, pedidoId: orderId, clienteId: client.id, quem: user.id, request: req })
        : null,
      quoteId,
      quoteNumber,
      orderId,
      orderNumber,
      total,
      paidTotal: 0,
      change: 0,
      clientName: client.company_name || client.name || "Cliente",
    });
  }

  for (const payment of resolvedPayments) {
    const mappedMethod = payment.mapped_method;

    const cobrancaPix = payment.pix_charge_id ? cobrancasPix.get(payment.pix_charge_id) : null;
    const paymentNotes =
      mappedMethod === "cash" && cashTotal > 0.0001
        ? `Pagamento registrado no PDV. Valor recebido: ${effectiveCashReceived.toFixed(2)}. Troco: ${change.toFixed(2)}.`
        : cobrancaPix
          ? `Pix pelo Mercado Pago no PDV. Pagamento ${cobrancaPix.provider_payment_id || "?"} (cobrança ${cobrancaPix.id}).`
          : "Pagamento registrado no PDV.";

    const { data: insertedPayment, error: paymentError } = await supabase
      .from("payments")
      .insert({
        user_id: client.user_id,
        order_id: orderId,
        bank_account_id: payment.resolved_bank_account_id,
        amount: payment.amount,
        payment_method: mappedMethod,
        payment_date: new Date().toISOString(),
        notes: paymentNotes,
        created_by: user.id,
      })
      .select("id")
      .single();

    if (paymentError) {
      return NextResponse.json(
        { error: "Pedido gerado, mas houve falha ao registrar um dos pagamentos.", details: paymentError.message, orderId, quoteId },
        { status: 500 }
      );
    }

    if (cobrancaPix) {
      /* A cobrança passa a apontar para a venda: é o que impede o mesmo Pix de
         fechar duas vendas e o que a auditoria procura. */
      await createAdminClient()
        .from("pdv_pix_charges")
        .update({ order_id: orderId, payment_id: insertedPayment?.id || null, updated_at: new Date().toISOString() })
        .eq("id", cobrancaPix.id);
    }

    if (mappedMethod === "cash" && activeCashSessionId) {
      const { error: movementError } = await supabase.from("pdv_cash_movements").insert({
        session_id: activeCashSessionId,
        company_id: ctx.companyId,
        movement_type: "sale_cash",
        direction: "IN",
        amount: payment.amount,
        description: `Entrada em dinheiro da venda PDV pedido #${orderNumber || ""}`.trim(),
        reference_payment_id: insertedPayment?.id || null,
        reference_order_id: orderId,
        created_by: user.id,
      });
      if (movementError) {
        return NextResponse.json(
          { error: "Pedido e pagamento criados, mas falhou ao registrar movimento do caixa.", details: movementError.message, orderId, quoteId },
          { status: 500 }
        );
      }
    }
  }

  if (change > 0.0001 && activeCashSessionId) {
    const { error: changeMovementError } = await supabase.from("pdv_cash_movements").insert({
      session_id: activeCashSessionId,
      company_id: ctx.companyId,
      movement_type: "change",
      direction: "OUT",
      amount: change,
      description: `Troco da venda PDV pedido #${orderNumber || ""}`.trim(),
      reference_order_id: orderId,
      cash_flow_id: null,
      created_by: user.id,
    });
    if (changeMovementError) {
      return NextResponse.json(
        {
          error: "Pedido e pagamentos criados, mas falhou ao registrar movimento operacional de troco no PDV.",
          details: changeMovementError.message,
          orderId,
          quoteId,
        },
        { status: 500 }
      );
    }
  }

  /*
    Venda fechada no balcao com pagamento registrado: e o mesmo aviso da venda
    online, para quem esta na rua e para quem esta no sistema. Se dois meios de
    pagamento foram usados, vale o de maior valor no titulo — o total continua
    inteiro no corpo.
  */
  try {
    const meioPrincipal = [...resolvedPayments].sort((a, b) => b.amount - a.amount)[0]?.mapped_method || null;
    const admin = createAdminClient();
    await avisarVendaAprovada(admin, {
      tenantId: client.user_id,
      valor: paidTotal,
      metodo: meioPrincipal,
      numero: orderNumber,
      cliente: client.company_name || client.name || null,
      orderId,
      origem: "sistema",
    });
    await avisarVenda(client.user_id, {
      valor: paidTotal,
      metodo: meioPrincipal,
      numero: orderNumber,
      cliente: client.company_name || client.name || null,
      orderId,
      origem: "sistema",
    });
  } catch {
    /* A venda ja esta fechada; o aviso e conveniencia. */
  }

  /* NFC-e: a venda não espera a SEFAZ. A nota vai para a fila e o cupom acompanha. */
  let notaFiscal: { situacao: string; motivo: string | null } | null = null;
  const pedidoDeNota = body?.nota_fiscal && typeof body.nota_fiscal === "object" ? (body.nota_fiscal as Record<string, unknown>) : null;
  if (pedidoDeNota?.emitir === true) {
    try {
      notaFiscal = await agendarNotaDaVenda({
        empresaId: client.user_id,
        pedidoId: orderId,
        usuarioId: user.id,
        documento: typeof pedidoDeNota.documento === "string" ? pedidoDeNota.documento : null,
        podeEmitir: canCompany(ctx, user.id, "fiscal.emitir"),
      });
    } catch (erro) {
      console.error("[fiscal] NFC-e da venda não agendada", orderId, erro instanceof Error ? erro.message : erro);
      notaFiscal = { situacao: "falhou", motivo: "A nota não entrou na fila. Emita pela tela do pedido." };
    }
  }

  /* Produto digital: a entrega sai agora e o balcão mostra o QR code do link. */
  const entregaDigital = temDigital
    ? await entregarDigitalDoBalcao({ lojaId: client.user_id, pedidoId: orderId, clienteId: client.id, quem: user.id, request: req })
    : null;

  return NextResponse.json({
    ok: true,
    entregaDigital,
    notaFiscal,
    quoteId,
    quoteNumber,
    orderId,
    orderNumber,
    total,
    paidTotal,
    change,
    clientName: client.company_name || client.name || "Cliente",
  });
}
