/* eslint-disable @typescript-eslint/no-explicit-any */

import { agendarNotaDaLoja } from "./nota-fiscal-da-loja";
import { minimoDaLinhaGravada } from "./adicionais";

type SupabaseLike = {
  from: (table: string) => any;
  rpc: (fn: string, params: Record<string, unknown>) => Promise<{ data: any; error: { message?: string } | null }>;
};

function formatQuantityValue(value: number) {
  return Number(value.toFixed(3)).toString().replace(".", ",");
}

function calculateEffectiveQuantity(item: {
  quantity: number;
  unit_type?: string | null;
  width_m?: number | null;
  height_m?: number | null;
  length_m?: number | null;
}) {
  const quantity = Number(item.quantity) || 0;
  if (quantity <= 0) return 0;

  const width = Number(item.width_m) || 0;
  const height = Number(item.height_m) || 0;
  const length = Number(item.length_m) || 0;
  const unitType = item.unit_type || "unit";

  if (unitType === "square_meter") {
    const area = width * height;
    return area > 0 ? quantity * area : quantity;
  }

  if (unitType === "linear_meter" || unitType === "meter") {
    return length > 0 ? quantity * length : quantity;
  }

  return quantity;
}

function checkSaleQuantityLimits(input: {
  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;
  effectiveQuantity: number;
}) {
  const minQty = input.min_sale_qty_enabled ? Number(input.min_sale_qty || 0) || null : null;
  const maxQty = input.max_sale_qty_enabled ? Number(input.max_sale_qty || 0) || null : null;
  const allowedQuantities = input.allowed_sale_qty_enabled
    ? (Array.isArray(input.allowed_sale_quantities) ? input.allowed_sale_quantities : [])
        .map((value) => Number(value || 0))
        .filter((value) => Number.isFinite(value) && value > 0)
    : [];
  const effectiveQuantity = Number(input.effectiveQuantity || 0) || 0;

  const isBelowMin = minQty !== null && effectiveQuantity < minQty;
  const isAboveMax = maxQty !== null && effectiveQuantity > maxQty;
  const isNotAllowedValue =
    allowedQuantities.length > 0 &&
    !allowedQuantities.some((value) => Math.abs(value - effectiveQuantity) <= 0.000001);

  return { minQty, maxQty, allowedQuantities, isBelowMin, isAboveMax, isNotAllowedValue };
}

function buildQuantityRuleMessage(
  product: {
    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;
  },
  effectiveQuantity: number
) {
  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,
  });

  if (quantityCheck.isBelowMin && quantityCheck.minQty !== null) {
    return `Quantidade minima atual: ${formatQuantityValue(quantityCheck.minQty)}.`;
  }
  if (quantityCheck.isAboveMax && quantityCheck.maxQty !== null) {
    return `Quantidade maxima atual: ${formatQuantityValue(quantityCheck.maxQty)}.`;
  }
  if (quantityCheck.isNotAllowedValue && quantityCheck.allowedQuantities.length > 0) {
    return `Quantidades permitidas atualmente: ${quantityCheck.allowedQuantities.map(formatQuantityValue).join(", ")}.`;
  }
  return null;
}

async function ensureCatalogPlaceholderClient(admin: SupabaseLike, tenantId: string) {
  const { data: existing, error: existingError } = await admin
    .from("clients")
    .select("id,user_id,name,company_name,is_deleted")
    .eq("user_id", tenantId)
    .eq("is_system_placeholder", true)
    .order("created_at", { ascending: true })
    .limit(1);

  if (existingError) throw new Error(existingError.message || "Nao foi possivel carregar o cliente tecnico.");

  const activeExisting = (existing || []).find((client: { is_deleted?: boolean | null }) => !client.is_deleted);
  if (activeExisting?.id) {
    return String(activeExisting.id);
  }

  const now = new Date().toISOString();
  const { data: created, error: createError } = await admin
    .from("clients")
    .insert([
      {
        user_id: tenantId,
        name: "Cliente Catalogo",
        company_name: "Cliente Catalogo",
        type: "pf",
        document: null,
        whatsapp: "",
        email: "",
        notes: "Cliente tecnico para pedidos do catalogo sem vinculo.",
        tags: ["catalog"],
        whatsapp_notify: false,
        active: true,
        address_zip: null,
        address_state: null,
        address_city: null,
        address_neighborhood: null,
        address_street: null,
        address_number: null,
        address_complement: null,
        created_at: now,
        updated_at: now,
        is_system_placeholder: true,
      },
    ])
    .select("id")
    .single();

  if (createError || !created?.id) {
    throw new Error(createError?.message || "Nao foi possivel criar cliente tecnico do catalogo.");
  }

  return String(created.id);
}

function mapCatalogPaymentMethod(method: string) {
  if (method === "pix") return "pix";
  if (method === "debit_card") return "debit_card";
  if (method === "credit_card") return "credit_card";
  return "transfer";
}

function extractCatalogOrderShipping(cartPayload: unknown) {
  if (!cartPayload || typeof cartPayload !== "object" || Array.isArray(cartPayload)) {
    return { amount: 0, details: null as Record<string, unknown> | null };
  }
  const shipping = (cartPayload as { shipping?: unknown }).shipping;
  if (!shipping || typeof shipping !== "object" || Array.isArray(shipping)) {
    return { amount: 0, details: null as Record<string, unknown> | null };
  }
  const shippingRecord = shipping as Record<string, unknown>;
  const option = shippingRecord.option && typeof shippingRecord.option === "object" && !Array.isArray(shippingRecord.option)
    ? (shippingRecord.option as Record<string, unknown>)
    : null;
  const amountCandidate = Number(shippingRecord.amount ?? option?.price ?? 0);
  const amount = Number.isFinite(amountCandidate) && amountCandidate > 0 ? amountCandidate : 0;
  /*
    Retirada no balcao costuma custar zero. Amarrar o detalhe ao valor fazia o
    pedido aprovado nascer sem nenhum vestigio de que o cliente vai buscar na
    loja: a equipe so via frete zerado e presumia entrega.
  */
  const hasChosenOption = Boolean(option?.id || option?.provider);
  return {
    amount,
    details: amount > 0 || hasChosenOption ? { ...shippingRecord, amount } : null,
  };
}

/*
  Trava de uma coisa só por vez (tabela travas_da_loja, migration 182).

  O aviso do Mercado Pago, a tela de retorno e a consulta de situação chegam
  quase juntos: cada um via "ainda não tem pedido" e criava o seu (a Espia
  comprou uma vez e nasceram 3 pedidos, 3 baixas e 3 entradas no caixa).
  Agora quem chega grava a chave; o segundo recebe duplicidade e espera o
  primeiro terminar, e então usa o que ele fez. Trava esquecida (o processo
  caiu no meio) vence em 2 minutos.
*/
const TRAVA_VENCIDA_MS = 2 * 60 * 1000;
const ESPERA_DA_TRAVA_MS = 400;
const TENTATIVAS_DA_TRAVA = 50;

async function umaVezSo<T>(
  admin: SupabaseLike,
  chave: string,
  jaFeito: () => Promise<T | null>,
  fazer: () => Promise<T>
): Promise<T> {
  for (let tentativa = 0; tentativa < TENTATIVAS_DA_TRAVA; tentativa += 1) {
    const pronto = await jaFeito();
    if (pronto) return pronto;

    const { error: travaError } = await admin.from("travas_da_loja").insert({ chave });
    if (!travaError) {
      try {
        const feitoNoMeioTempo = await jaFeito();
        if (feitoNoMeioTempo) return feitoNoMeioTempo;
        return await fazer();
      } finally {
        await admin.from("travas_da_loja").delete().eq("chave", chave);
      }
    }
    if (travaError.code !== "23505") {
      throw new Error(travaError.message || "Nao foi possivel travar a operacao.");
    }

    const { data: trava } = await admin.from("travas_da_loja").select("criado_em").eq("chave", chave).maybeSingle();
    if (trava?.criado_em && Date.now() - new Date(trava.criado_em).getTime() > TRAVA_VENCIDA_MS) {
      await admin.from("travas_da_loja").delete().eq("chave", chave).eq("criado_em", trava.criado_em);
      continue;
    }
    await new Promise((resolve) => setTimeout(resolve, ESPERA_DA_TRAVA_MS));
  }
  throw new Error("O pedido ainda esta sendo registrado. Tente de novo em instantes.");
}

export async function ensureApprovedOrderFromCatalogOrder(admin: SupabaseLike, catalogOrderId: string): Promise<string> {
  return umaVezSo(
    admin,
    `pedido-da-loja:${catalogOrderId}`,
    async () => {
      const { data } = await admin.from("catalog_orders").select("approved_order_id").eq("id", catalogOrderId).maybeSingle();
      return data?.approved_order_id ? String(data.approved_order_id) : null;
    },
    () => converterPedidoDaLoja(admin, catalogOrderId)
  );
}

async function converterPedidoDaLoja(admin: SupabaseLike, catalogOrderId: string) {
  const { data: orderRow, error: orderError } = await admin
    .from("catalog_orders")
    .select(
      "id,user_id,number,status,client_id,customer_name,customer_whatsapp,notes,subtotal,discount,total,currency,coupon_code,cart_payload,approved_order_id,catalog_order_items(id,product_id,title,description,quantity,unit_type,width_m,height_m,length_m,unit_price,total_price,selected_options,raw_item)"
    )
    .eq("id", catalogOrderId)
    .maybeSingle();

  if (orderError || !orderRow) {
    throw new Error(orderError?.message || "Pedido do catalogo nao encontrado.");
  }

  if (orderRow.approved_order_id) {
    return String(orderRow.approved_order_id);
  }

  const items = (orderRow.catalog_order_items || []).filter((item: { quantity?: number | null }) => Number(item.quantity || 0) > 0);
  if (items.length === 0) {
    throw new Error("Pedido do catalogo sem itens validos para aprovacao.");
  }

  let clientId = orderRow.client_id ? String(orderRow.client_id) : null;
  if (clientId) {
    const { data: client } = await admin
      .from("clients")
      .select("id,is_deleted")
      .eq("id", clientId)
      .maybeSingle();
    if (!client || client.is_deleted) {
      clientId = null;
    }
  }

  if (!clientId) {
    clientId = await ensureCatalogPlaceholderClient(admin, String(orderRow.user_id));
  }

  const now = new Date().toISOString();
  const shippingSnapshot = extractCatalogOrderShipping((orderRow as { cart_payload?: unknown } | null)?.cart_payload);
  const internalNotes = ["origem:catalogo", orderRow.coupon_code ? `cupom:${orderRow.coupon_code}` : ""]
    .filter(Boolean)
    .join("\n");

  /* O número do orçamento técnico é max + 1. Dois pagamentos da mesma loja
     chegando juntos (ou a loja e o painel ao mesmo tempo) calculavam o mesmo
     número e o segundo quebrava no índice único (quotes_user_number_key): o
     cliente pagava e o pedido não nascia na hora. Agora, se o número já foi
     usado, lê o maior de novo (o vizinho já gravou) e tenta o próximo, até
     6 vezes. No caso normal o número é exatamente o de sempre. */
  const proximoNumero = async () => {
    const { data: maxQuote } = await admin
      .from("quotes")
      .select("number")
      .eq("user_id", orderRow.user_id)
      .order("number", { ascending: false })
      .limit(1)
      .maybeSingle();
    return Math.max(1, Number(maxQuote?.number || 0) + 1);
  };
  const ehColisaoDeNumero = (erro: { code?: string; message?: string } | null) =>
    Boolean(erro) && (erro?.code === "23505" || /quotes_user_number_key/i.test(String(erro?.message || "")));

  let createdQuote: { id?: string } | null = null;
  let quoteError: { code?: string; message?: string } | null = null;
  for (let tentativa = 0; tentativa < 6; tentativa += 1) {
    const quoteNumber = await proximoNumero();
    const resposta = await admin
    .from("quotes")
    .insert([
      {
        user_id: orderRow.user_id,
        client_id: clientId,
        number: quoteNumber,
        status: "draft",
        title: `Pedido do catalogo #${orderRow.number}`,
        description: null,
        currency: orderRow.currency || "BRL",
        subtotal: Number(orderRow.subtotal || 0),
        discount: Number(orderRow.discount || 0),
        shipping: shippingSnapshot.amount,
        shipping_details: shippingSnapshot.details,
        list_price_mode: false,
        total: Number(orderRow.total || 0),
        expires_at: null,
        delivery_date: null,
        notes: orderRow.notes || null,
        internal_notes: internalNotes,
        /* O cliente já pagou (ou o pedido é de valor zero): quantidade e
           estoque não podem mais recusar o pedido. Ele nasce, e a falta vira
           "reposição pendente" (migration 208). Antes o pagamento entrava e o
           pedido não nascia. */
        venda_ja_paga: true,
        created_at: now,
        updated_at: now,
      },
    ])
    .select("id")
    .single();
    createdQuote = (resposta.data as { id?: string } | null) ?? null;
    quoteError = (resposta.error as { code?: string; message?: string } | null) ?? null;
    if (!ehColisaoDeNumero(quoteError)) break;
  }

  if (quoteError || !createdQuote?.id) {
    throw new Error(quoteError?.message || "Nao foi possivel criar o orcamento tecnico.");
  }

  const quoteId = String(createdQuote.id);
  const productIds = Array.from(
    new Set(
      items
        .map((item: { product_id?: string | null }) => item.product_id)
        .filter((value: string | null | undefined): value is string => typeof value === "string" && value.length > 0)
    )
  );
  const productsById = new Map<string, any>();

  if (productIds.length > 0) {
    const { data: productsData, error: productsError } = await admin
      .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")
      .in("id", productIds)
      .eq("user_id", orderRow.user_id);

    if (productsError) {
      await admin.from("quotes").delete().eq("id", quoteId);
      throw new Error(productsError.message || "Nao foi possivel validar os produtos do pedido.");
    }

    for (const product of productsData || []) {
      productsById.set(String(product.id), product);
    }
  }

  const quoteItems = items.map((item: any, index: number) => {
    const product = item.product_id ? productsById.get(item.product_id) : null;
    const effectiveQuantity = calculateEffectiveQuantity({
      quantity: Number(item.quantity || 0),
      unit_type: item.unit_type || "unit",
      width_m: item.width_m,
      height_m: item.height_m,
      length_m: item.length_m,
    });
    const quantityRuleMessage = product ? buildQuantityRuleMessage(product, effectiveQuantity) : null;
    const baseDescription = String(item.description || "").trim();
    /* Fora da regra de quantidade de hoje: o item continua ligado ao produto
       (estoque, ficha de produção, nota fiscal e custo dependem disso) e só
       ganha o aviso. Antes ele virava avulso (product_id vazio) e saía do
       estoque, da produção e da nota. */
    const fallbackDescription = quantityRuleMessage
      ? [baseDescription, `Quantidade fora da regra atual do produto ao gerar o pedido da loja. ${quantityRuleMessage}`]
          .filter(Boolean)
          .join("\n")
      : baseDescription || null;
    /* O valor mínimo do item vem gravado no item da loja (raw_item); o total
       do item já é o mínimo. Vai para o orçamento técnico e dele ao pedido. */
    const minimo = minimoDaLinhaGravada(item.raw_item);

    return {
      quote_id: quoteId,
      position: index + 1,
      product_id: item.product_id || null,
      unit_type: item.unit_type || "unit",
      width_m: item.width_m,
      height_m: item.height_m,
      length_m: item.length_m,
      title: item.title,
      description: fallbackDescription,
      quantity: Number(item.quantity || 0),
      unit_price: Number(item.unit_price || 0),
      unit_cost: 0,
      line_total: Number(item.total_price || Number(item.unit_price || 0) * Number(item.quantity || 0)),
      selected_options: item.selected_options || null,
      valor_minimo_aplicado: minimo ? minimo.minimo : null,
      valor_sem_minimo: minimo ? minimo.semMinimo : null,
    };
  });

  const { error: quoteItemsError } = await admin.from("quote_items").insert(quoteItems);
  if (quoteItemsError) {
    await admin.from("quotes").delete().eq("id", quoteId);
    throw new Error(quoteItemsError.message || "Nao foi possivel criar os itens do orcamento.");
  }

  const { data: approveResult, error: approveError } = await admin.rpc("approve_quote_and_generate_order", { p_quote_id: quoteId });
  if (approveError) {
    throw new Error(approveError.message || "Nao foi possivel gerar o pedido interno.");
  }

  const orderId = String((approveResult as { order_id?: string } | null)?.order_id || "");
  if (!orderId) {
    throw new Error("Nao foi possivel identificar o pedido interno gerado.");
  }

  await admin.from("orders").update({ internal_notes: internalNotes }).eq("id", orderId);
  await admin
    .from("catalog_orders")
    .update({
      status: "approved",
      approved_order_id: orderId,
      approved_at: now,
      approved_by: orderRow.user_id,
      updated_at: now,
    })
    .eq("id", catalogOrderId);

  return orderId;
}

/*
  Pedido de valor zero (cupom de 100%, ou tudo de graça).

  Não há o que cobrar, então não há aviso de pagamento para esperar: o pedido
  vira pedido interno na hora, como um Pix aprovado, e nasce quitado. Antes a
  tela pedia o pagamento de R$ 0, o provedor recusava e o cliente via um erro
  com o pedido já criado e a loja já avisada.

  `sync_order_payment_summary` só roda quando um pagamento entra ou sai; sem
  pagamento nenhum, o pedido de total zero ficaria "não pago" para sempre.
*/
export async function confirmarPedidoSemValor(admin: SupabaseLike, catalogOrderId: string) {
  const orderId = await ensureApprovedOrderFromCatalogOrder(admin, catalogOrderId);
  await admin.rpc("sync_order_payment_summary", { p_order_id: orderId });
  await admin
    .from("catalog_orders")
    .update({
      payment_status: "approved",
      payment_required: false,
      paid_at: new Date().toISOString(),
      updated_at: new Date().toISOString(),
    })
    .eq("id", catalogOrderId);
  return orderId;
}

export async function applyApprovedCatalogPayment(admin: SupabaseLike, catalogPaymentId: string) {
  const { data: paymentRow, error: paymentError } = await admin
    .from("catalog_order_payments")
    .select(
      "id,user_id,catalog_order_id,status,payment_method,amount,approved_at,external_reference,provider_payment_id,applied_payment_id,metadata,integration_id"
    )
    .eq("id", catalogPaymentId)
    .maybeSingle();

  if (paymentError || !paymentRow) {
    throw new Error(paymentError?.message || "Pagamento do catalogo nao encontrado.");
  }

  if (paymentRow.status !== "approved") {
    return { appliedPaymentId: paymentRow.applied_payment_id || null, orderId: null };
  }

  const orderId = await ensureApprovedOrderFromCatalogOrder(admin, String(paymentRow.catalog_order_id));

  if (paymentRow.applied_payment_id) {
    return { appliedPaymentId: String(paymentRow.applied_payment_id), orderId };
  }

  /* A baixa também é uma só, mesmo com três avisos do mesmo pagamento chegando juntos. */
  const appliedPaymentId = await umaVezSo(
    admin,
    `pagamento-da-loja:${catalogPaymentId}`,
    async () => {
      const { data } = await admin.from("catalog_order_payments").select("applied_payment_id").eq("id", catalogPaymentId).maybeSingle();
      return data?.applied_payment_id ? String(data.applied_payment_id) : null;
    },
    () => darBaixaDoPagamento(admin, paymentRow, orderId)
  );
  return { appliedPaymentId, orderId };
}

async function darBaixaDoPagamento(
  admin: SupabaseLike,
  paymentRow: {
    id: string;
    user_id: string;
    payment_method: string | null;
    amount: number | string | null;
    approved_at: string | null;
    external_reference: string | null;
    provider_payment_id: string | null;
    metadata: unknown;
  },
  orderId: string
): Promise<string> {
  const catalogPaymentId = String(paymentRow.id);

  const metadata = paymentRow.metadata && typeof paymentRow.metadata === "object" && !Array.isArray(paymentRow.metadata)
    ? (paymentRow.metadata as Record<string, unknown>)
    : {};
  const bankAccountId = typeof metadata.bank_account_id === "string" && metadata.bank_account_id.trim()
    ? metadata.bank_account_id.trim()
    : null;

  const notes = [
    "Pagamento automatico Mercado Pago",
    paymentRow.provider_payment_id ? `payment_id:${paymentRow.provider_payment_id}` : "",
    paymentRow.external_reference ? `external_reference:${paymentRow.external_reference}` : "",
  ]
    .filter(Boolean)
    .join("\n");

  const { data: createdPayment, error: insertError } = await admin
    .from("payments")
    .insert({
      user_id: paymentRow.user_id,
      order_id: orderId,
      amount: Number(paymentRow.amount || 0),
      payment_method: mapCatalogPaymentMethod(String(paymentRow.payment_method || "")),
      payment_date: paymentRow.approved_at || new Date().toISOString(),
      notes,
      bank_account_id: bankAccountId,
      created_by: paymentRow.user_id,
    })
    .select("id")
    .single();

  if (insertError || !createdPayment?.id) {
    throw new Error(insertError?.message || "Nao foi possivel registrar a baixa automatica do pedido.");
  }

  await admin
    .from("catalog_order_payments")
    .update({
      applied_payment_id: createdPayment.id,
      updated_at: new Date().toISOString(),
    })
    .eq("id", catalogPaymentId);

  /* Venda paga na loja: a NF-e entra na fila se a empresa ligou a emissão automática. */
  await agendarNotaDaLoja(admin, String(paymentRow.user_id), orderId);

  return String(createdPayment.id);
}
