import crypto from "crypto";
import type { SupabaseClient, User } from "@supabase/supabase-js";
import { canCompany, getCompanyContext } from "@/lib/company-context";
import { checkSaleQuantityLimits } from "@/lib/price-tiers";
import { ApiV1Error } from "@/lib/api-v1/errors";
import { erroDasCondicoes, normalizarCondicoes } from "@/lib/condicoes-comerciais";
import { prazoDoLink } from "@/lib/prazo-do-link";
import { herdarCamposDoOrcamento } from "@/lib/campos-proprios-do-banco";

type QuoteStatus = "draft" | "sent" | "approved" | "rejected" | "production" | "delivered";

export type QuoteListItem = {
  id: string;
  number: number;
  status: string;
  total: number;
  created_at: string | null;
  updated_at: string | null;
  client_name: string | null;
};

export type QuoteItemSummary = {
  quote_id: string;
  product_id: string | null;
  title: string;
  quantity: number;
  total_price: number;
};

type IncomingItem = {
  id?: string;
  product_id: string | null;
  unit_type: string;
  width_m: number | null;
  height_m: number | null;
  length_m: number | null;
  title: string;
  description: string | null;
  quantity: number;
  unit_price: number;
  unit_cost: number;
  selected_options?: Record<string, unknown> | null;
  /** Valor mínimo do item que pegou nesta linha (migration 194): a linha vale ele. */
  valor_minimo_aplicado?: number | null;
  valor_sem_minimo?: number | null;
};

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

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

type ProductSaleQuantityRow = {
  id: string;
  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;
};

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

function getErrorMessage(error: unknown, fallback: string) {
  if (error instanceof Error && error.message) return error.message;
  if (isRecord(error) && typeof error.message === "string" && error.message.trim()) return error.message;
  return fallback;
}

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 generateShareToken() {
  return crypto.randomBytes(24).toString("hex");
}

async function resolveQuoteReadContext(params: { supabase: SupabaseClient; user: User }) {
  const ctx = await getCompanyContext(params.supabase, params.user);
  if (!canCompany(ctx, params.user.id, "quotes.read")) {
    throw new ApiV1Error(403, "forbidden", "Sem permissao para visualizar orcamentos.");
  }

  return {
    ctx,
    tenantIds: ctx.tenantIds?.length ? ctx.tenantIds : [params.user.id],
  };
}

function mapQuoteListItem(row: any): QuoteListItem {
  return {
    id: String(row.id),
    number: Number(row.number || 0),
    status: String(row.status || "draft"),
    total: Number(row.total || 0),
    created_at: row.created_at || null,
    updated_at: row.updated_at || null,
    client_name: row.clients?.company_name || row.clients?.name || null,
  };
}

function effectiveQuantity(item: Pick<IncomingItem, "quantity" | "unit_type" | "width_m" | "height_m" | "length_m">) {
  const quantity = item.quantity || 0;
  if (quantity <= 0) return 0;

  if (item.unit_type === "square_meter") {
    const area = (item.width_m || 0) * (item.height_m || 0);
    return area > 0 ? quantity * area : quantity;
  }

  if (item.unit_type === "linear_meter" || item.unit_type === "meter") {
    const length = item.length_m || 0;
    return length > 0 ? quantity * length : quantity;
  }

  return quantity;
}

function normalizePublicBaseUrl(origin: string) {
  const resolved = process.env.NEXT_PUBLIC_PUBLIC_CATALOG_BASE_URL || origin;
  const normalized = resolved.replace(":3002", ":3001").replace(/\/\/admin\./, "//");
  return normalized.replace(/\/$/, "");
}

const QUICK_PLACEHOLDER_NAME = "Cliente pendente";
const QUICK_PLACEHOLDER_COMPANY = "Orcamento rapido";
const QUICK_PLACEHOLDER_NOTES = "Cliente tecnico para orcamentos rapidos.";

function isMissingColumnError(error: unknown, columnName: string) {
  if (!error || typeof error !== "object") return false;
  const message =
    typeof (error as { message?: unknown }).message === "string"
      ? ((error as { message?: string }).message || "").toLowerCase()
      : "";
  return message.includes(columnName.toLowerCase());
}

async function ensureQuickPlaceholderClient(supabase: SupabaseClient, tenantId: string) {
  const selectColumns = "id,user_id,name,company_name";
  let supportsFlagColumn = true;

  const findExisting = async () => {
    if (!supportsFlagColumn) {
      return supabase
        .from("clients")
        .select(selectColumns)
        .eq("user_id", tenantId)
        .eq("name", QUICK_PLACEHOLDER_NAME)
        .eq("company_name", QUICK_PLACEHOLDER_COMPANY)
        .order("created_at", { ascending: true })
        .limit(1)
        .maybeSingle();
    }

    return supabase
      .from("clients")
      .select(selectColumns)
      .eq("user_id", tenantId)
      .eq("is_system_placeholder", true)
      .order("created_at", { ascending: true })
      .limit(1)
      .maybeSingle();
  };

  let { data: existing, error: existingErr } = await findExisting();
  if (existingErr && isMissingColumnError(existingErr, "is_system_placeholder")) {
    supportsFlagColumn = false;
    const retry = await findExisting();
    existing = retry.data;
    existingErr = retry.error;
  }

  if (existingErr) throw existingErr;
  if (existing) return existing;

  const now = new Date().toISOString();
  let insertPayload: Record<string, unknown> = {
    user_id: tenantId,
    name: QUICK_PLACEHOLDER_NAME,
    company_name: QUICK_PLACEHOLDER_COMPANY,
    type: "pf",
    active: false,
    notes: QUICK_PLACEHOLDER_NOTES,
    created_at: now,
    updated_at: now,
  };

  if (supportsFlagColumn) insertPayload.is_system_placeholder = true;

  let finalCreated: Record<string, unknown> | null = null;
  let finalError: unknown = null;
  for (let attempt = 0; attempt < 4; attempt++) {
    const insertion = await supabase.from("clients").insert([insertPayload]).select(selectColumns).single();
    finalCreated = insertion.data as Record<string, unknown> | null;
    finalError = insertion.error;
    if (!finalError) break;

    const optionalColumn = ["is_system_placeholder", "active", "notes"].find(
      (column) => Object.prototype.hasOwnProperty.call(insertPayload, column) && isMissingColumnError(finalError, column)
    );

    if (!optionalColumn) break;
    if (optionalColumn === "is_system_placeholder") supportsFlagColumn = false;
    const fallbackPayload = { ...insertPayload };
    delete fallbackPayload[optionalColumn];
    insertPayload = fallbackPayload;
  }

  if (finalError) {
    const code =
      typeof finalError === "object" && finalError !== null && "code" in finalError
        ? (finalError as { code?: string }).code
        : undefined;

    if (code === "23503") {
      throw new Error("Nao foi possivel criar cliente tecnico para este usuario. Tente sair e entrar novamente.");
    }
    if (code === "42501") {
      throw new Error("Sem permissao para criar o cliente tecnico do Orcamento Rapido.");
    }
    if (code === "23505") {
      const retry = await findExisting();
      if (retry.error) throw retry.error;
      if (retry.data) return retry.data;
    }
    throw finalError;
  }

  return finalCreated;
}

async function getNextQuoteNumber(supabase: SupabaseClient, 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 getQuoteNumberStartFrom(supabase: SupabaseClient, tenantId: string) {
  const { data, error } = await supabase.from("profiles").select("quote_number_start_from").eq("id", tenantId).maybeSingle();
  if (error) {
    if ((error as any).code === "PGRST116" || isMissingColumnError(error, "quote_number_start_from")) return 1;
    throw error;
  }

  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 getNextQuoteNumberWithFloor(supabase: SupabaseClient, tenantId: string, startFrom: number) {
  const sequential = await getNextQuoteNumber(supabase, tenantId);
  return Math.max(sequential, Math.max(1, Math.floor(startFrom || 1)));
}

function normalizeItem(raw: unknown): IncomingItem | null {
  if (!isRecord(raw)) return null;

  const id = typeof raw.id === "string" && isUuid(raw.id) ? raw.id : undefined;
  const title = typeof raw.title === "string" ? raw.title.trim() : "";
  const description = typeof raw.description === "string" ? raw.description.trim() : "";
  const unitType = typeof raw.unit_type === "string" ? raw.unit_type : "unit";
  const productId = typeof raw.product_id === "string" && raw.product_id ? raw.product_id : null;

  const quantity = typeof raw.quantity === "number" && Number.isFinite(raw.quantity) ? raw.quantity : 0;
  const unitPrice = typeof raw.unit_price === "number" && Number.isFinite(raw.unit_price) ? raw.unit_price : 0;
  const unitCost = typeof raw.unit_cost === "number" && Number.isFinite(raw.unit_cost) ? raw.unit_cost : 0;

  const width = typeof raw.width_m === "number" && Number.isFinite(raw.width_m) && raw.width_m > 0 ? raw.width_m : null;
  const height = typeof raw.height_m === "number" && Number.isFinite(raw.height_m) && raw.height_m > 0 ? raw.height_m : null;
  const length = typeof raw.length_m === "number" && Number.isFinite(raw.length_m) && raw.length_m > 0 ? raw.length_m : null;
  const selectedOptions = isRecord(raw.selected_options) ? raw.selected_options : null;
  const minimo = typeof raw.valor_minimo_aplicado === "number" && Number.isFinite(raw.valor_minimo_aplicado) && raw.valor_minimo_aplicado > 0
    ? Math.round(raw.valor_minimo_aplicado * 100) / 100
    : null;
  const semMinimo = minimo !== null && typeof raw.valor_sem_minimo === "number" && Number.isFinite(raw.valor_sem_minimo) && raw.valor_sem_minimo >= 0
    ? Math.round(raw.valor_sem_minimo * 100) / 100
    : null;

  if (!title && !description) return null;

  return {
    id,
    product_id: productId,
    unit_type: unitType || "unit",
    width_m: width,
    height_m: height,
    length_m: length,
    title: title || "Item",
    description: description || null,
    quantity: Math.max(0, quantity || 0),
    unit_price: Math.max(0, unitPrice || 0),
    unit_cost: Math.max(0, unitCost || 0),
    selected_options: selectedOptions,
    valor_minimo_aplicado: minimo,
    valor_sem_minimo: semMinimo,
  };
}

export async function listQuotes(params: { supabase: SupabaseClient; user: User; request: Request }) {
  const { tenantIds } = await resolveQuoteReadContext(params);
  const { searchParams } = new URL(params.request.url);
  const q = (searchParams.get("q") || "").trim();
  let query = params.supabase
    .from("quotes")
    .select("id,number,status,total,created_at,updated_at,clients(name,company_name)")
    .in("user_id", tenantIds)
    .order("created_at", { ascending: false })
    .limit(100);

  if (q) query = query.or(`title.ilike.%${q}%,notes.ilike.%${q}%`);

  const { data, error } = await query;
  if (error) throw new ApiV1Error(400, "quotes_fetch_failed", error.message);

  return {
    ok: true,
    data: (data || []).map((row: any) => mapQuoteListItem(row)),
  };
}

export async function listQuotesByCustomer(params: {
  supabase: SupabaseClient;
  user: User;
  clientId: string;
  limit?: number;
}) {
  const { tenantIds } = await resolveQuoteReadContext(params);

  let query = params.supabase
    .from("quotes")
    .select("id,number,status,total,created_at,updated_at,clients(name,company_name)")
    .eq("client_id", params.clientId)
    .in("user_id", tenantIds)
    .order("created_at", { ascending: false });

  if (params.limit && params.limit > 0) {
    query = query.limit(params.limit);
  }

  const { data, error } = await query;
  if (error) throw new ApiV1Error(400, "quotes_fetch_failed", error.message);

  return {
    ok: true,
    data: ((data || []) as any[]).map((row) => mapQuoteListItem(row)),
  };
}

export async function listQuoteItemsByQuoteIds(params: {
  supabase: SupabaseClient;
  user: User;
  quoteIds: string[];
}) {
  await resolveQuoteReadContext(params);

  if (!params.quoteIds.length) {
    return {
      ok: true,
      data: [] as QuoteItemSummary[],
    };
  }

  const { data, error } = await params.supabase
    .from("quote_items")
    .select("quote_id,product_id,title,quantity,total_price")
    .in("quote_id", params.quoteIds);

  if (error) throw new ApiV1Error(400, "quote_items_fetch_failed", error.message);

  return {
    ok: true,
    data: ((data || []) as Array<Record<string, unknown>>).map((row) => ({
      quote_id: String(row.quote_id || ""),
      product_id: row.product_id ? String(row.product_id) : null,
      title: String(row.title || ""),
      quantity: Number(row.quantity || 0),
      total_price: Number(row.total_price || 0),
    })),
  };
}

export async function createQuote(params: {
  supabase: SupabaseClient;
  user: User;
  request: Request;
  body: Record<string, unknown> | null;
  /* Quem chama entrega o share_url a alguém (a API externa devolve o link para
     a extensão colar no WhatsApp). Para o link, é o mesmo que o "Copiar link"
     do painel: o prazo que a empresa ligou começa a contar agora e o envio
     fica nas métricas do link. A tela do painel não passa isto: lá o prazo só
     começa quando alguém copia ou manda o link. */
  entregaDoLink?: { admin: SupabaseClient };
}) {
  const { supabase, user, request, body } = params;
  const ctx = await getCompanyContext(supabase, user);
  if (!canCompany(ctx, user.id, "quotes.write")) {
    throw new ApiV1Error(403, "forbidden", "Sem permissao.");
  }

  const isQuickQuote = body?.quick_quote === true;
  const tenantIds = ctx.tenantIds?.length ? ctx.tenantIds : [user.id];
  const quickTenantId = ctx.ownerId || user.id;

  let clientId = typeof body?.client_id === "string" ? body.client_id.trim() : "";
  if (!clientId && isQuickQuote) {
    try {
      const quickClient = await ensureQuickPlaceholderClient(supabase, quickTenantId);
      clientId = String((quickClient as { id?: unknown } | null)?.id || "");
    } catch (error) {
      throw new ApiV1Error(400, "quick_quote_client_failed", getErrorMessage(error, "Nao foi possivel preparar cliente tecnico."));
    }
  }

  if (!clientId) throw new ApiV1Error(400, "missing_client_id", "Campo 'client_id' e obrigatorio.");

  const incomingItems = Array.isArray(body?.items) ? body?.items : [];
  const items = incomingItems.map(normalizeItem).filter(Boolean) as IncomingItem[];
  if (!items.length) throw new ApiV1Error(400, "missing_items", "Adicione ao menos 1 item no orcamento.");

  const status = typeof body?.status === "string" ? (body.status as QuoteStatus) : "draft";
  const title = typeof body?.title === "string" ? body.title.trim() : "";
  const expiresAt = typeof body?.expires_at === "string" && body.expires_at ? body.expires_at : null;
  const deliveryDate = typeof body?.delivery_date === "string" && body.delivery_date ? body.delivery_date : null;
  const notes = typeof body?.notes === "string" ? body.notes.trim() : "";
  const internalNotes = typeof body?.internal_notes === "string" ? body.internal_notes.trim() : "";
  const discount = typeof body?.discount === "number" && Number.isFinite(body.discount) ? body.discount : 0;
  const shipping = typeof body?.shipping === "number" && Number.isFinite(body.shipping) ? body.shipping : 0;
  const shippingDetails = typeof body?.shipping_details === "object" && body.shipping_details ? body.shipping_details : null;
  const listPriceMode = body?.list_price_mode === true;
  /* As condições comerciais chegam como JSON e passam pela mesma leitura
     defensiva da tela; nulo é "sem condições", como nos orçamentos antigos. */
  const paymentTerms = normalizarCondicoes(body?.payment_terms);
  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 effectivePrimarySellerUserId =
    requestedPrimarySellerUserId || (isQuickQuote && isUuid(user.id) ? user.id : "") || (ctx.ownerId && isUuid(ctx.ownerId) ? ctx.ownerId : "");
  const requestedAllocations = Array.isArray(body?.allocations)
    ? body.allocations.map(normalizeAllocation).filter(Boolean) as IncomingAllocation[]
    : [];

  const subtotal = items.reduce((acc, item) => acc + totalDoItem(item), 0);
  const total = Math.max(0, subtotal - discount + shipping);

  if (paymentTerms) {
    const erroDasCondicoesComerciais = erroDasCondicoes(paymentTerms, total);
    if (erroDasCondicoesComerciais) throw new ApiV1Error(400, "invalid_payment_terms", erroDasCondicoesComerciais);
  }

  const { data: clientRow, error: clientErr } = await supabase
    .from("clients")
    .select("id,user_id,name,company_name,whatsapp,whatsapp_notify")
    .eq("id", clientId)
    .in("user_id", tenantIds)
    .maybeSingle();

  if (clientErr) throw new ApiV1Error(400, "client_fetch_failed", clientErr.message);
  if (!clientRow) throw new ApiV1Error(404, "client_not_found", "Cliente nao encontrado.");

  const clientTenantId = (clientRow as { user_id?: unknown } | null)?.user_id as string;
  if (!clientTenantId) throw new ApiV1Error(400, "invalid_client", "Cliente invalido.");

  const sellerIds = Array.from(
    new Set([effectivePrimarySellerUserId, ...requestedAllocations.map((allocation) => allocation.seller_user_id)].filter(Boolean))
  );
  if (sellerMode === "single" && !effectivePrimarySellerUserId) {
    throw new ApiV1Error(400, "missing_primary_seller", "Selecione o vendedor principal do orcamento.");
  }
  if (sellerMode === "split" && requestedAllocations.length < 2) {
    throw new ApiV1Error(400, "invalid_allocations", "Informe ao menos dois vendedores para o rateio.");
  }

  const duplicateSellerId = requestedAllocations.find(
    (allocation, index) =>
      requestedAllocations.findIndex((candidate) => candidate.seller_user_id === allocation.seller_user_id) !== index
  )?.seller_user_id;
  if (duplicateSellerId) {
    throw new ApiV1Error(400, "duplicate_seller", "O mesmo vendedor nao pode aparecer mais de uma vez no rateio.");
  }

  const rawAllocationTotal = requestedAllocations.reduce((sum, allocation) => sum + allocation.allocation_percent, 0);
  if (sellerMode === "split" && Math.abs(rawAllocationTotal - 100) > 0.01) {
    throw new ApiV1Error(400, "invalid_allocations", "O rateio deve totalizar exatamente 100%.");
  }

  if (sellerIds.length === 0) {
    throw new ApiV1Error(400, "missing_sellers", "Nenhum vendedor valido foi informado.");
  }

  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) throw new ApiV1Error(400, "seller_validation_failed", "Falha ao validar vendedores da empresa.");

  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) throw new ApiV1Error(400, "invalid_seller", "Ha vendedor invalido ou inativo no orcamento.");

  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: effectivePrimarySellerUserId,
          allocations: [
            {
              seller_user_id: effectivePrimarySellerUserId,
              allocation_percent: 100,
              is_primary: true,
            },
          ],
        };

  const productIds = Array.from(new Set(items.map((item) => item.product_id).filter((productId): productId is string => Boolean(productId))));
  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")
      .eq("user_id", clientTenantId)
      .in("id", productIds);

    if (productsForLimitsError) throw new ApiV1Error(400, "products_fetch_failed", productsForLimitsError.message);

    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) throw new ApiV1Error(400, "invalid_product", `Produto vinculado ao item "${item.title}" nao foi encontrado.`);

      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 && quantityCheck.minQty !== null) {
        throw new ApiV1Error(400, "invalid_quantity", `Item "${item.title}": quantidade minima ${quantityCheck.minQty}.`);
      }
      if (quantityCheck.isAboveMax && quantityCheck.maxQty !== null) {
        throw new ApiV1Error(400, "invalid_quantity", `Item "${item.title}": quantidade maxima ${quantityCheck.maxQty}.`);
      }
      if (quantityCheck.isNotAllowedValue && quantityCheck.allowedQuantities.length > 0) {
        throw new ApiV1Error(400, "invalid_quantity", `Item "${item.title}": use uma quantidade permitida.`);
      }
    }
  }

  let quoteNumberFloor = 1;
  try {
    quoteNumberFloor = await getQuoteNumberStartFrom(supabase, clientTenantId);
  } catch (error) {
    throw new ApiV1Error(400, "quote_number_config_failed", getErrorMessage(error, "Falha ao carregar numeracao de orcamento."));
  }

  let createdId = "";
  let quoteNumber = 0;
  let lastErr: unknown = null;
  for (let attempt = 0; attempt < 4; attempt++) {
    try {
      const number = await getNextQuoteNumberWithFloor(supabase, clientTenantId, quoteNumberFloor);
      const now = new Date().toISOString();

      const { data: created, error } = await supabase
        .from("quotes")
        .insert([
          {
            user_id: clientTenantId,
            client_id: clientId,
            number,
            status,
            title: title || null,
            description: null,
            currency: "BRL",
            subtotal,
            discount,
            shipping,
            shipping_details: shippingDetails,
            list_price_mode: listPriceMode,
            payment_terms: paymentTerms,
            seller_mode: salesAssignment.sellerMode,
            primary_seller_user_id: salesAssignment.primarySellerUserId || null,
            total,
            expires_at: expiresAt,
            delivery_date: deliveryDate,
            notes: notes || null,
            internal_notes: internalNotes || null,
            created_at: now,
            updated_at: now,
          },
        ])
        .select("id,number")
        .single();

      if (error) {
        const code = typeof error === "object" && error !== null && "code" in error ? (error as { code?: string }).code : undefined;
        if (code === "23505") {
          lastErr = error;
          continue;
        }
        throw error;
      }

      createdId = (created as { id: string }).id;
      quoteNumber = Number((created as { number: number }).number);
      break;
    } catch (error) {
      lastErr = error;
    }
  }

  if (!createdId) {
    const message = lastErr instanceof Error ? lastErr.message : "Falha ao criar orcamento.";
    throw new ApiV1Error(400, "quote_create_failed", message);
  }

  const itemsPayload = items.map((item, idx) => {
    const payload = {
      quote_id: createdId,
      position: idx + 1,
      product_id: item.product_id,
      unit_type: item.unit_type,
      width_m: item.width_m,
      height_m: item.height_m,
      length_m: item.length_m,
      title: item.title,
      description: item.description,
      quantity: item.quantity,
      unit_price: item.unit_price,
      unit_cost: item.unit_cost,
      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,
    };

    if (item.id) {
      return { id: item.id, ...payload };
    }
    return payload;
  });

  const { error: itemsError } = await supabase.from("quote_items").insert(itemsPayload);
  if (itemsError) {
    await supabase.from("quotes").delete().eq("id", createdId);
    throw new ApiV1Error(400, "quote_items_create_failed", itemsError.message);
  }

  const allocationsPayload = salesAssignment.allocations.map((allocation) => ({
    company_id: ctx.companyId,
    quote_id: createdId,
    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", createdId);
    throw new ApiV1Error(400, "quote_allocations_create_failed", allocationsError.message);
  }

  const origin = new URL(request.url).origin;
  const publicBaseUrl = normalizePublicBaseUrl(origin);
  const profileUserId = ctx.ownerId ?? ctx.companyId;
  const { data: profile } = await supabase
    .from("profiles")
    .select("id,slug,quote_share_link_expires_enabled,quote_share_link_expires_days")
    .eq("id", profileUserId)
    .maybeSingle();
  const { data: customDomainRow } = await supabase
    .from("profile_custom_domains")
    .select("domain,verified")
    .eq("profile_id", profileUserId)
    .maybeSingle();

  let shareUrl = "";
  let shareToken = generateShareToken();
  const perfilDoLink = profile as { quote_share_link_expires_enabled?: boolean | null; quote_share_link_expires_days?: number | null } | null;
  const prazoDoLinkEntregue = params.entregaDoLink
    ? { share_expires_at: prazoDoLink(perfilDoLink?.quote_share_link_expires_enabled, perfilDoLink?.quote_share_link_expires_days) }
    : {};
  if ((profile as { slug?: unknown } | null)?.slug) {
    for (let attempt = 0; attempt < 2; attempt++) {
      const { error: updateError } = await supabase
        .from("quotes")
        .update({ share_enabled: true, share_token: shareToken, ...prazoDoLinkEntregue })
        .eq("id", createdId);

      if (!updateError) {
        const customDomain = String((customDomainRow as { domain?: unknown } | null)?.domain || "").trim().toLowerCase();
        const customDomainVerified = Boolean((customDomainRow as { verified?: unknown } | null)?.verified);
        shareUrl =
          customDomain && customDomainVerified
            ? `https://${customDomain}/o/${shareToken}`
            : `${publicBaseUrl}/${String((profile as { slug?: unknown }).slug)}/o/${shareToken}`;
        break;
      }

      const code =
        typeof updateError === "object" && updateError !== null && "code" in updateError
          ? (updateError as { code?: string }).code
          : undefined;
      if (code === "23505") {
        shareToken = generateShareToken();
        continue;
      }
      break;
    }
  }

  if (shareUrl && params.entregaDoLink) {
    /* O registro é um extra: se falhar, o orçamento e o link continuam valendo. */
    const { error: envioError } = await params.entregaDoLink.admin
      .from("quote_link_shares")
      .insert({ quote_id: createdId, company_id: clientTenantId, user_id: user.id, channel: "link" });
    if (envioError) console.error("Nao deu para registrar o envio do link:", envioError.message);
  }

  return {
    ok: true,
    quoteId: createdId,
    quoteNumber,
    total,
    expiresAt,
    shareUrl,
    client: {
      name: String((clientRow as any)?.company_name || (clientRow as any)?.name || "cliente"),
      whatsapp: String((clientRow as any)?.whatsapp || ""),
      whatsappNotify: Boolean((clientRow as any)?.whatsapp_notify ?? true),
    },
  };
}

export async function approveQuote(params: { supabase: SupabaseClient; user: User; quoteId: string }) {
  const { supabase, user, quoteId } = params;
  const companyCtx = await getCompanyContext(supabase, user);
  if (!canCompany(companyCtx, user.id, "orders.write")) {
    throw new ApiV1Error(403, "forbidden", "Sem permissao.");
  }

  const { data, error } = await supabase.rpc("approve_quote_and_generate_order", { p_quote_id: quoteId });
  if (error) throw new ApiV1Error(400, "quote_approval_failed", error.message);

  const orderId = (data as { order_id?: unknown } | null)?.order_id;
  if (typeof orderId !== "string" || !orderId) {
    throw new ApiV1Error(400, "invalid_order_response", "Resposta invalida ao gerar pedido.");
  }

  /* Campos próprios do orçamento com o mesmo campo no pedido passam junto
     (migration 187). Não derruba a aprovação se falhar. */
  await herdarCamposDoOrcamento(supabase, quoteId, orderId);

  const tenantIds = companyCtx.tenantIds?.length ? companyCtx.tenantIds : [user.id];
  const { data: quoteRow } = await supabase
    .from("quotes")
    .select("id,user_id,internal_notes")
    .eq("id", quoteId)
    .in("user_id", tenantIds)
    .maybeSingle();

  const catalogOrderMatch = String((quoteRow as { internal_notes?: string | null } | null)?.internal_notes || "").match(
    /catalog_order_id:([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/i
  );
  if (catalogOrderMatch?.[1]) {
    await supabase
      .from("catalog_orders")
      .update({
        status: "approved",
        approved_order_id: orderId,
        approved_at: new Date().toISOString(),
        approved_by: user.id,
        quote_id: quoteId,
        updated_at: new Date().toISOString(),
      })
      .eq("id", catalogOrderMatch[1])
      .in("user_id", tenantIds);
  }

  const { data: orderRow } = await supabase
    .from("orders")
    .select("id,number,status,clients(name,company_name,whatsapp,whatsapp_notify),quotes(number)")
    .eq("id", orderId)
    .in("user_id", tenantIds)
    .maybeSingle();

  return {
    ok: true,
    orderId,
    notification: orderRow
      ? {
          clientName: (orderRow as any)?.clients?.company_name || (orderRow as any)?.clients?.name || "cliente",
          whatsapp: (orderRow as any)?.clients?.whatsapp || "",
          whatsappNotify: Boolean((orderRow as any)?.clients?.whatsapp_notify ?? true),
          orderNumber: typeof (orderRow as any)?.number === "number" ? (orderRow as any).number : null,
          quoteNumber: Array.isArray((orderRow as any)?.quotes)
            ? (orderRow as any)?.quotes?.[0]?.number ?? null
            : (orderRow as any)?.quotes?.number ?? null,
          status: (orderRow as any)?.status || "",
        }
      : null,
  };
}
