import { resolveTierPrice, type ProductPriceTier } from "@/lib/price-tiers";
import { somarAdicionais, valorMinimoDoItem, type OpcaoComPreco } from "@/lib/adicionais";

/*
  A aritmética do carrinho do PDV.

  Aqui não tem tela: são as regras de quanto custa cada linha — preço por
  faixa de quantidade, adicional de variação (por peça ou uma vez só), item
  por metro quadrado, quantidade mínima e quantidades permitidas. Saíram da
  página inteiras, sem mudar uma vírgula, no dia em que o PDV ganhou cara
  nova: o desenho podia mudar, a conta não.
*/

export type ProductRow = {
  id: string;
  name: string;
  price: number | null;
  unit_type?: string | null;
  use_variation_price_only?: boolean | null;
  image_url: string | null;
  category_id: string | null;
  active: boolean | null;
  inventory_scope?: "product" | "option" | "variant" | null;
  inventory_attribute_name?: string | null;
  product_attributes?: ProductAttributeValue[] | 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;
  product_price_tiers?: ProductPriceTier[] | null;
  sku?: string | null;
  barcode?: string | null;
  /* "digital": a venda pede o cliente e mostra o QR code da entrega. */
  product_kind?: string | null;
  /* Valor mínimo do item (migration 194). */
  price_on_request?: boolean | null;
  valor_minimo_item?: number | string | null;
  valor_minimo_pdv?: boolean | null;
};

export type CategoryRow = {
  id: string;
  name: string;
};

export type ClientRow = {
  id: string;
  name: string;
  company_name: string | null;
  is_deleted?: boolean | null;
};

export type SellerOption = {
  user_id: string;
  email: string;
  role: string;
};

export type SellerAllocationDraft = {
  id: string;
  seller_user_id: string;
  allocation_percent: string;
};

export type CartItem = {
  id: string;
  product_id: string | null;
  title: string;
  quantity: number;
  unit_price: number;
  unit_type?: string | null;
  width_m?: number | null;
  height_m?: number | null;
  sku?: string | null;
  barcode?: string | null;
  note: string;
  is_free_item: boolean;
  selected_options: SelectedOptionsPayload | null;
  /** O valor mínimo do item que vale no PDV para este produto; null = sem.
      Preço digitado à mão tira o mínimo da linha. */
  valor_minimo_item?: number | null;
};

export type PaymentMethod = "pix" | "card" | "cash";

export type PaymentSplit = {
  id: string;
  method: PaymentMethod;
  amount_input: string;
};

export type ProfileRow = {
  id: string;
  store_name: string | null;
  catalog_name: string | null;
  logo_url: string | null;
  catalog_logo_url: string | null;
  cnpj?: string | null;
  address?: string | null;
  company_phone?: string | null;
  phone?: string | null;
  company_whatsapp?: string | null;
  whatsapp?: string | null;
};

export type ProductAttributeOptionValue = {
  id?: string | null;
  label: string;
  price?: number | null;
  price_applies_per_quantity?: boolean | null;
  price_unit?: string | null;
  stock?: number | null;
  is_required?: boolean | null;
  position?: number | null;
};

export type ProductAttributeValue = {
  name: string;
  options?: Array<string | ProductAttributeOptionValue> | null;
  allow_multiple?: boolean | null;
  is_required?: boolean | null;
  position?: number | null;
};

export type SelectedOptionsPayload = {
  inventory_scope: "product" | "option" | "variant";
  inventory_attribute_name: string | null;
  options: Array<{
    option_id: string | null;
    attribute_name: string;
    label: string;
    price?: number | null;
    price_applies_per_quantity?: boolean | null;
    price_unit?: string | null;
    affects_stock: boolean;
  }>;
};

export type BankAccountRow = {
  id: string;
  name: string | null;
  bank_name: string | null;
  active?: boolean | null;
  type?: string | null;
};

export type CashSession = {
  id: string;
  company_id: string;
  bank_account_id: string;
  status: "open" | "closed";
  opening_float: number;
  opened_at: string;
  closed_at?: string | null;
  /* O que foi contado na gaveta no fechamento, e quanto isso diferiu do
     esperado. A rota sempre devolveu; a tela e que nao mostrava. */
  closing_counted_amount?: number | null;
  difference_amount?: number | null;
};

export type CashSessionTotals = {
  expected_balance: number;
  /* Entradas e saidas somadas. Sem elas o historico mostrava tres numeros que
     nao explicavam o saldo, e nao havia como conferir a gaveta pela tela. */
  total_in?: number;
  total_out?: number;
  cash_sales: number;
  supplies: number;
  withdrawals: number;
  change: number;
};

export type CashMovement = {
  id: string;
  movement_type: string;
  direction: "IN" | "OUT";
  amount: number;
  description: string | null;
  created_at: string;
};

export function cashMovementLabel(movementType: string) {
  if (movementType === "opening") return "Abertura";
  if (movementType === "supply") return "Suprimento";
  if (movementType === "withdrawal") return "Sangria";
  if (movementType === "sale_cash") return "Venda dinheiro";
  if (movementType === "change") return "Troco";
  if (movementType === "close_adjustment") return "Ajuste fechamento";
  return movementType;
}

/* A reversao de uma venda cancelada e gravada como `close_adjustment` com esta
   marca na descricao. Chamar isso de "ajuste de fechamento" no historico faz
   parecer erro de contagem, quando foi o sistema devolvendo o dinheiro de uma
   venda que a pessoa cancelou. */
const MARCA_DA_REVERSAO = "[PDV:REVERSAO]";

export function ehReversaoDeVenda(descricao: string | null | undefined) {
  return String(descricao || "").includes(MARCA_DA_REVERSAO);
}

/** O rotulo que a pessoa le, ja considerando a descricao do movimento. */
export function rotuloDoMovimento(movimento: { movement_type: string; description?: string | null }) {
  if (ehReversaoDeVenda(movimento.description)) return "Estorno de venda";
  return cashMovementLabel(movimento.movement_type);
}

/** A descricao sem a marca tecnica, que nao diz nada a quem opera o caixa. */
export function descricaoDoMovimento(descricao: string | null | undefined) {
  return String(descricao || "").replace(MARCA_DA_REVERSAO, "").trim();
}

export function makeId() {
  if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
  return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
}

export function sanitizeQty(value: number) {
  if (!Number.isFinite(value)) return 1;
  return Math.max(1, Math.floor(value));
}

export function getProductMinQuantity(product?: ProductRow | null) {
  if (!product) return 1;
  const allowedQuantities = getProductAllowedQuantities(product);
  if (Boolean(product.allowed_sale_qty_enabled) && allowedQuantities.length > 0) {
    return allowedQuantities[0];
  }
  if (!product.min_sale_qty_enabled) return 1;
  const raw = Number(product.min_sale_qty);
  if (!Number.isFinite(raw) || raw <= 0) return 1;
  return Math.max(1, Math.ceil(raw));
}

export function getProductAllowedQuantities(product?: ProductRow | null) {
  if (!product) return [] as number[];
  return Array.isArray(product.allowed_sale_quantities)
    ? product.allowed_sale_quantities
        .map((value) => Number(value))
        .filter((value) => Number.isFinite(value) && value > 0)
        .sort((a, b) => a - b)
    : [];
}

export function resolveNextQuantityForProduct(product: ProductRow | null, current: number, delta: number) {
  if (!product) return sanitizeQty(current + delta);
  const allowedQuantities = getProductAllowedQuantities(product);
  const usesAllowed = Boolean(product.allowed_sale_qty_enabled) && allowedQuantities.length > 0;
  if (!usesAllowed) return sanitizeQty(current + delta);
  const sorted = allowedQuantities;
  const currentIndex = sorted.findIndex((value) => Math.abs(value - current) <= 0.000001);
  if (currentIndex === -1) {
    return delta >= 0 ? sorted[0] : sorted[Math.max(0, sorted.length - 1)];
  }
  if (delta >= 0) {
    return sorted[Math.min(sorted.length - 1, currentIndex + 1)];
  }
  return sorted[Math.max(0, currentIndex - 1)];
}

export function normalizeAttributeOption(option: string | ProductAttributeOptionValue, index: number) {
  if (typeof option === "string") {
    return {
      id: null,
      label: option,
      price: 0,
      price_unit: null,
      stock: null,
      is_required: false,
      position: index,
    };
  }
  return {
    id: option.id ?? null,
    label: option.label,
    price: Number(option.price) || 0,
    price_applies_per_quantity: option.price_applies_per_quantity !== false,
    price_unit: typeof option.price_unit === "string" ? option.price_unit : null,
    stock: Number.isFinite(Number(option.stock)) ? Number(option.stock) : null,
    is_required: Boolean(option.is_required),
    position: typeof option.position === "number" ? option.position : index,
  };
}

export function getSortedAttributes(product: ProductRow | null) {
  return (product?.product_attributes || [])
    .filter((attribute) => Boolean(attribute?.name))
    .slice()
    .sort((a, b) => Number(a.position ?? 0) - Number(b.position ?? 0));
}

export function getItemVariationMap(selectedOptions: SelectedOptionsPayload | null | undefined) {
  const options = Array.isArray(selectedOptions?.options) ? selectedOptions.options : [];
  const map: Record<string, string[]> = {};
  for (const row of options) {
    const attributeName = typeof row.attribute_name === "string" ? row.attribute_name : "";
    const label = typeof row.label === "string" ? row.label : "";
    if (!attributeName || !label) continue;
    const list = map[attributeName] || [];
    if (!list.includes(label)) list.push(label);
    map[attributeName] = list;
  }
  return map;
}

export function buildSelectedOptionsPayload(product: ProductRow, selectionMap: Record<string, string[]>) {
  const attributes = getSortedAttributes(product);
  const inventoryScope = (product.inventory_scope || "product") as "product" | "option" | "variant";
  const inventoryAttributeName = (product.inventory_attribute_name || "").trim().toLowerCase();
  const payloadOptions: Array<{
    option_id: string | null;
    attribute_name: string;
    label: string;
    price: number;
    price_applies_per_quantity: boolean;
    price_unit?: string | null;
    affects_stock: boolean;
  }> = [];

  for (const attribute of attributes) {
    const normalizedOptions = (attribute.options || [])
      .map((option, index) => normalizeAttributeOption(option, index))
      .sort((a, b) => Number(a.position ?? 0) - Number(b.position ?? 0));
    const selectedLabels = selectionMap[attribute.name] || [];
    for (const selectedLabel of selectedLabels) {
      const selectedOption = normalizedOptions.find((option) => option.label === selectedLabel);
      if (!selectedOption) continue;
      payloadOptions.push({
        option_id: selectedOption.id || null,
        attribute_name: attribute.name,
        label: selectedOption.label,
        price: Number(selectedOption.price) || 0,
        price_applies_per_quantity: selectedOption.price_applies_per_quantity !== false,
        price_unit: selectedOption.price_unit ?? null,
        affects_stock:
          inventoryScope === "option" &&
          attribute.name.trim().toLowerCase() === inventoryAttributeName,
      });
    }
  }

  if (!payloadOptions.length) return null;

  return {
    inventory_scope: inventoryScope,
    inventory_attribute_name: inventoryScope === "option" ? product.inventory_attribute_name || null : null,
    options: payloadOptions,
  } as SelectedOptionsPayload;
}

export function getSelectedOptionsUnitPriceAdjustment(
  selectedOptions: SelectedOptionsPayload | null,
  product?: ProductRow | null,
  quantity = 1
) {
  if (!product || !selectedOptions) return 0;
  const selectedMap = getItemVariationMap(selectedOptions);
  const attributes = getSortedAttributes(product);
  const escolhidas: OpcaoComPreco[] = [];
  for (const attribute of attributes) {
    const selectedLabels = selectedMap[attribute.name] || [];
    if (!selectedLabels.length) continue;
    const normalizedOptions = (attribute.options || [])
      .map((option, index) => normalizeAttributeOption(option, index))
      .sort((a, b) => Number(a.position ?? 0) - Number(b.position ?? 0));
    for (const selectedLabel of selectedLabels) {
      const selectedOption = normalizedOptions.find((option) => option.label === selectedLabel);
      if (!selectedOption) continue;
      escolhidas.push(selectedOption);
    }
  }
  /* O PDV vende peças sem medida: por medida e por peça dão no mesmo. */
  const soma = somarAdicionais(escolhidas, (product as { unit_type?: string | null }).unit_type);
  const safeQuantity = quantity > 0 ? quantity : 1;
  return soma.porMedida + soma.porPeca + soma.umaVez / safeQuantity;
}

export function resolveItemUnitPrice(product: ProductRow, selectedOptions: SelectedOptionsPayload | null, quantity = 1) {
  const basePrice = Number(product.price ?? 0) || 0;
  const tierBasePrice = product.use_variation_price_only
    ? 0
    : resolveTierPrice(basePrice, quantity, product.product_price_tiers || []).unitPrice;
  const optionsAdjustment = getSelectedOptionsUnitPriceAdjustment(selectedOptions, product, quantity);
  if (product.use_variation_price_only) {
    return Math.max(0, optionsAdjustment);
  }
  return Math.max(0, tierBasePrice + optionsAdjustment);
}

export function getSelectedOptionsSummary(selectedOptions: SelectedOptionsPayload | null) {
  const map = getItemVariationMap(selectedOptions);
  const parts = Object.entries(map)
    .filter(([, labels]) => labels.length > 0)
    .map(([attribute, labels]) => `${attribute}: ${labels.join(", ")}`);
  return parts.join(" / ");
}

export function buildDefaultSelectedOptions(product: ProductRow) {
  const attributes = getSortedAttributes(product);
  const selectionMap: Record<string, string[]> = {};
  for (const attribute of attributes) {
    const normalizedOptions = (attribute.options || [])
      .map((option, index) => normalizeAttributeOption(option, index))
      .sort((a, b) => Number(a.position ?? 0) - Number(b.position ?? 0));
    if (!normalizedOptions.length) continue;
    selectionMap[attribute.name] = [normalizedOptions[0].label];
  }
  return buildSelectedOptionsPayload(product, selectionMap);
}

/** O mínimo do item que vale no PDV para o produto (null = sem). */
export function minimoDoProdutoNoPdv(product: ProductRow | null | undefined): number | null {
  return valorMinimoDoItem(product, "pdv");
}

/* A conta da linha sem o mínimo: no item por m², peças × área × preço do metro. */
function totalAutomaticoDaLinha(item: CartItem) {
  if (item.unit_type === "square_meter") {
    const area = (item.width_m || 1) * (item.height_m || 1);
    return item.unit_price * area * Math.max(1, item.quantity || 1);
  }
  return item.unit_price * item.quantity;
}

/**
 * O valor mínimo do item na linha do PDV: a linha inteira (todas as peças)
 * comparada sozinha com o mínimo. Null quando não pegou.
 */
export function minimoDaLinhaDoPdv(item: CartItem): { minimo: number; semMinimo: number } | null {
  const minimo = Number(item.valor_minimo_item);
  if (item.is_free_item || !item.product_id || !(minimo > 0)) return null;
  const automatico = Math.round(totalAutomaticoDaLinha(item) * 100) / 100;
  return automatico < minimo ? { minimo, semMinimo: automatico } : null;
}

/* Quanto vale a linha: a conta automática, ou o mínimo do item quando ela dá menos. */
export function totalDaLinha(item: CartItem) {
  const minimo = minimoDaLinhaDoPdv(item);
  return minimo ? minimo.minimo : totalAutomaticoDaLinha(item);
}

export function subtotalDoCarrinho(cart: CartItem[]) {
  return cart.reduce((soma, item) => soma + totalDaLinha(item), 0);
}
