import { NextResponse } from "next/server";
import { createAdminClient, createClient } from "@/lib/supabase-server";
import { canCompany, getCompanyContext } from "@/lib/company-context";
import {
  buildSupplierPurchaseOrderPublicUrl,
  createSupplierPurchaseOrderPublicToken,
  DIAS_DE_VALIDADE_DO_LINK,
} from "@/lib/supplier-purchase-order-public";
import { origemPublicaDoAdmin } from "@/lib/origem-publica";
import { numeroDoTexto } from "@/lib/numero-do-campo";

type Params = { params: Promise<{ id: string }> };
type PurchaseStatus = "draft" | "ordered" | "partial" | "received" | "canceled";

function normalizeString(value: unknown) {
  return typeof value === "string" ? value.trim() : "";
}

/**
 * "1.5" virava 15 e "10.50" virava 1050.
 *
 * O leitor antigo apagava TODO ponto e so aceitava virgula, entao quem digita
 * no teclado numerico ou cola de uma planilha gravava dez ou cem vezes mais.
 * A tela usava exatamente a mesma conta, entao nao havia validacao que
 * pegasse: a compra e a conta a pagar nasciam erradas juntas.
 *
 * Agora e a mesma leitura do resto do admin, que entende "0,160", "0.160" e
 * "1.234,56". Campo vazio continua devolvendo NaN de proposito: e assim que a
 * linha sem preenchimento e descartada logo abaixo.
 */
function normalizeAmount(value: unknown) {
  if (typeof value === "number") return Number.isFinite(value) ? value : NaN;
  if (typeof value !== "string") return NaN;
  if (!value.trim()) return NaN;
  return numeroDoTexto(value);
}

function isIsoDate(value: string | null) {
  return Boolean(value && /^\d{4}-\d{2}-\d{2}$/.test(value));
}

function roundCurrency(value: number) {
  return Math.round(value * 100) / 100;
}

function roundPrice(value: number) {
  return Math.round(value * 10000) / 10000;
}

/**
 * Frete, desconto e imposto: campo em branco vale zero, e nao erro.
 *
 * `normalizeAmount` devolve NaN para vazio de proposito, porque em quantidade
 * "nao preencheu" e diferente de "preencheu zero". Aqui os tres sao opcionais
 * por natureza: a compra sem frete e a compra com frete zero sao a mesma
 * compra.
 */
function valorExtra(value: unknown) {
  if (value === null || value === undefined || value === "") return 0;
  const numero = normalizeAmount(value);
  if (!Number.isFinite(numero)) return NaN;
  return roundCurrency(numero);
}

function normalizePurchaseStatus(value: unknown): PurchaseStatus {
  const normalized = normalizeString(value).toLowerCase();
  if (
    normalized === "draft" ||
    normalized === "ordered" ||
    normalized === "partial" ||
    normalized === "received" ||
    normalized === "canceled"
  ) {
    return normalized;
  }
  return "draft";
}

function extractMissingAccountsPayableColumn(error: { message?: string | null; details?: string | null; hint?: string | null } | null) {
  const raw = `${error?.message || ""} ${error?.details || ""} ${error?.hint || ""}`;
  const match =
    raw.match(/Could not find the '([^']+)' column of 'accounts_payable'/i) ||
    raw.match(/column\s+["']?accounts_payable\.?([^"'\s]+)["']?\s+does not exist/i) ||
    raw.match(/column\s+["']?([^"'\s]+)["']?\s+does not exist/i);
  return match?.[1]?.trim() || null;
}

async function insertAccountsPayableWithFallback(
  admin: ReturnType<typeof createAdminClient>,
  payload: Record<string, unknown>
) {
  const optionalColumns = new Set(["notes", "purchase_order_id", "supplier_id", "supplier_name"]);
  const attempted = new Set<string>();
  let currentPayload = { ...payload };
  let lastError: { message?: string | null; details?: string | null; hint?: string | null } | null = null;

  for (let attempt = 0; attempt < optionalColumns.size + 1; attempt += 1) {
    const { data, error } = await admin.from("accounts_payable").insert(currentPayload).select("id").single();
    if (!error && data?.id) {
      return { data, error: null as null };
    }

    lastError = error;
    const missingColumn = extractMissingAccountsPayableColumn(error);
    if (!missingColumn || !optionalColumns.has(missingColumn) || attempted.has(missingColumn)) {
      return { data: null, error };
    }

    attempted.add(missingColumn);
    const nextPayload = { ...currentPayload };
    delete nextPayload[missingColumn];
    currentPayload = nextPayload;
  }

  return { data: null, error: lastError };
}

async function loadPurchasePayablesWithFallback(
  admin: ReturnType<typeof createAdminClient>,
  companyId: string,
  purchaseOrderIds: string[]
) {
  if (purchaseOrderIds.length === 0) {
    return { data: [] as Array<Record<string, unknown>>, error: null as null };
  }

  const optionalColumns = new Set(["supplier_id", "supplier_name", "purchase_order_id"]);
  let selectedColumns = [
    "id",
    "purchase_order_id",
    "amount",
    "due_date",
    "payment_date",
    "status",
    "description",
    "supplier_id",
    "supplier_name",
  ];
  let lastError: { message?: string | null; details?: string | null; hint?: string | null } | null = null;

  for (let attempt = 0; attempt < optionalColumns.size + 1; attempt += 1) {
    const { data, error } = await admin
      .from("accounts_payable")
      .select(selectedColumns.join(","))
      .eq("company_id", companyId)
      .in("purchase_order_id", purchaseOrderIds);

    if (!error) {
      return { data: (data ?? []) as unknown as Array<Record<string, unknown>>, error: null as null };
    }

    lastError = error;
    const missingColumn = extractMissingAccountsPayableColumn(error);
    if (!missingColumn || !optionalColumns.has(missingColumn)) {
      return { data: [] as Array<Record<string, unknown>>, error };
    }

    // Bancos antigos podem não ter os campos de vínculo da compra.
    // Nesse caso a página continua carregando, apenas sem contas vinculadas.
    if (missingColumn === "purchase_order_id") {
      return { data: [] as Array<Record<string, unknown>>, error: null as null };
    }

    selectedColumns = selectedColumns.filter((column) => column !== missingColumn);
  }

  return { data: [] as Array<Record<string, unknown>>, error: lastError };
}

async function getAuth(permission: "suppliers.read" | "suppliers.list.write") {
  const supabase = await createClient();
  const {
    data: { user },
    error,
  } = await supabase.auth.getUser();
  if (error || !user) return { ok: false as const, status: 401, user: null, ctx: null };
  const ctx = await getCompanyContext(supabase, user);
  if (!canCompany(ctx, user.id, permission)) return { ok: false as const, status: 403, user, ctx: null };
  return { ok: true as const, status: 200, user, ctx };
}

async function insertActivity(
  admin: ReturnType<typeof createAdminClient>,
  companyId: string,
  supplierId: string,
  actorUserId: string | null,
  entityId: string | null,
  action: string,
  description: string,
  payload: Record<string, unknown> = {}
) {
  await admin.from("supplier_activity_log").insert({
    company_id: companyId,
    supplier_id: supplierId,
    entity_type: "purchase_order",
    entity_id: entityId,
    action,
    description,
    payload,
    actor_user_id: actorUserId,
  });
}

async function resolveCategoryId(
  admin: ReturnType<typeof createAdminClient>,
  companyId: string,
  rawValue: unknown
) {
  const value = normalizeString(rawValue);
  if (!value) return null;

  const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
  if (isUuid) return value;

  const { data: existing } = await admin
    .from("financial_categories")
    .select("id")
    .eq("company_id", companyId)
    .ilike("name", value)
    .maybeSingle();

  if (existing?.id) return existing.id as string;

  const { data: created, error } = await admin
    .from("financial_categories")
    .insert({
      company_id: companyId,
      name: value,
      icon: "shopping-cart",
      type: "expense",
    })
    .select("id")
    .single();

  if (error || !created?.id) {
    throw new Error("Falha ao processar a categoria financeira.");
  }

  return created.id as string;
}

async function resolveSupplierMaterial(
  admin: ReturnType<typeof createAdminClient>,
  companyId: string,
  supplierId: string,
  actorUserId: string | null,
  input: {
    materialId?: unknown;
    name?: unknown;
    unit?: unknown;
    notes?: unknown;
  }
) {
  const rawMaterialId = normalizeString(input.materialId);
  const rawName = normalizeString(input.name);
  const rawUnit = normalizeString(input.unit) || "un";
  const rawNotes = normalizeString(input.notes) || null;

  if (rawMaterialId) {
    const { data: existing, error } = await admin
      .from("supplier_materials")
      .select("id,name,unit,last_unit_price,last_purchase_at,notes,sku")
      .eq("company_id", companyId)
      .eq("supplier_id", supplierId)
      .eq("id", rawMaterialId)
      .is("deleted_at", null)
      .maybeSingle();

    if (error) throw new Error("Falha ao validar material do fornecedor.");
    if (!existing?.id) throw new Error("Material do fornecedor inválido.");

    const nextName = rawName || String(existing.name || "");
    const nextUnit = rawUnit || String(existing.unit || "un");
    if (nextName !== existing.name || nextUnit !== existing.unit || rawNotes !== (existing.notes ?? null)) {
      await admin
        .from("supplier_materials")
        .update({
          name: nextName,
          unit: nextUnit,
          notes: rawNotes,
        })
        .eq("company_id", companyId)
        .eq("supplier_id", supplierId)
        .eq("id", rawMaterialId);
    }

    return {
      id: String(existing.id),
      name: nextName,
      unit: nextUnit,
      lastUnitPrice:
        existing.last_unit_price === null || existing.last_unit_price === undefined
          ? null
          : Number(existing.last_unit_price),
      lastPurchaseAt: typeof existing.last_purchase_at === "string" ? existing.last_purchase_at : null,
      notes: rawNotes ?? (typeof existing.notes === "string" ? existing.notes : null),
      sku: typeof existing.sku === "string" ? existing.sku : null,
    };
  }

  if (!rawName) throw new Error("Informe o material/insumo do item.");

  const { data: existingByName, error: existingError } = await admin
    .from("supplier_materials")
    .select("id,name,unit,last_unit_price,last_purchase_at,notes,sku")
    .eq("company_id", companyId)
    .eq("supplier_id", supplierId)
    .ilike("name", rawName)
    .is("deleted_at", null)
    .maybeSingle();

  if (existingError) throw new Error("Falha ao validar material do fornecedor.");

  if (existingByName?.id) {
    const nextUnit = rawUnit || String(existingByName.unit || "un");
    if (nextUnit !== existingByName.unit || rawNotes !== (existingByName.notes ?? null)) {
      await admin
        .from("supplier_materials")
        .update({
          unit: nextUnit,
          notes: rawNotes,
        })
        .eq("company_id", companyId)
        .eq("supplier_id", supplierId)
        .eq("id", existingByName.id);
    }

    return {
      id: String(existingByName.id),
      name: String(existingByName.name || rawName),
      unit: nextUnit,
      lastUnitPrice:
        existingByName.last_unit_price === null || existingByName.last_unit_price === undefined
          ? null
          : Number(existingByName.last_unit_price),
      lastPurchaseAt: typeof existingByName.last_purchase_at === "string" ? existingByName.last_purchase_at : null,
      notes: rawNotes ?? (typeof existingByName.notes === "string" ? existingByName.notes : null),
      sku: typeof existingByName.sku === "string" ? existingByName.sku : null,
    };
  }

  const { data: created, error: createdError } = await admin
    .from("supplier_materials")
    .insert({
      company_id: companyId,
      supplier_id: supplierId,
      name: rawName,
      unit: rawUnit,
      notes: rawNotes,
      created_by: actorUserId,
    })
    .select("id,name,unit,last_unit_price,last_purchase_at,notes,sku")
    .single();

  if (createdError || !created?.id) {
    throw new Error("Falha ao cadastrar material do fornecedor.");
  }

  return {
    id: String(created.id),
    name: String(created.name || rawName),
    unit: String(created.unit || rawUnit),
    lastUnitPrice:
      created.last_unit_price === null || created.last_unit_price === undefined ? null : Number(created.last_unit_price),
    lastPurchaseAt: typeof created.last_purchase_at === "string" ? created.last_purchase_at : null,
    notes: typeof created.notes === "string" ? created.notes : rawNotes,
    sku: typeof created.sku === "string" ? created.sku : null,
  };
}

export async function GET(req: Request, { params }: Params) {
  try {
    const auth = await getAuth("suppliers.read");
    if (!auth.ok || !auth.ctx) {
      return NextResponse.json({ error: auth.status === 401 ? "Não autenticado." : "Sem permissão." }, { status: auth.status });
    }

    const { id } = await params;
    const admin = createAdminClient();
    const origin = origemPublicaDoAdmin(req);

    const [{ data: supplier }, { data, error }] = await Promise.all([
      admin.from("suppliers").select("id,name").eq("company_id", auth.ctx.companyId).eq("id", id).is("deleted_at", null).maybeSingle(),
      admin
        .from("supplier_purchase_orders")
        .select(
          "id,order_number,description,total_amount,shipping_amount,discount_amount,tax_amount,extras_no_custo,status,ordered_at,expected_at,notes,created_at,updated_at,public_link_version"
        )
        .eq("company_id", auth.ctx.companyId)
        .eq("supplier_id", id)
        .order("created_at", { ascending: false }),
    ]);

    if (!supplier?.id) return NextResponse.json({ error: "Fornecedor não encontrado." }, { status: 404 });
    if (error) return NextResponse.json({ error: "Falha ao carregar compras do fornecedor." }, { status: 500 });

    const purchaseOrderIds = (data ?? [])
      .map((item) => (typeof item.id === "string" ? item.id : ""))
      .filter(Boolean);

    let payableMap = new Map<
      string,
      {
        id: string;
        amount: number;
        dueDate: string | null;
        paymentDate: string | null;
        status: string;
        description: string;
        supplierId: string | null;
        supplierName: string | null;
      }
    >();

    const [payablesResult, itemsResult, materialsResult] = await Promise.all([
      loadPurchasePayablesWithFallback(admin, auth.ctx.companyId, purchaseOrderIds),
      purchaseOrderIds.length > 0
        ? admin
            .from("supplier_purchase_order_items")
            .select(
              "id,purchase_order_id,material_id,material_name,unit,quantity,received_quantity,unit_price,total_price,landed_unit_price,previous_unit_price,price_change,price_change_percent,notes,created_at"
            )
            .eq("company_id", auth.ctx.companyId)
            .eq("supplier_id", id)
            .in("purchase_order_id", purchaseOrderIds)
            .order("created_at", { ascending: true })
        : Promise.resolve({ data: [], error: null }),
      admin
        .from("supplier_materials")
        .select("id,name,unit,last_unit_price,last_purchase_at,notes,sku")
        .eq("company_id", auth.ctx.companyId)
        .eq("supplier_id", id)
        .is("deleted_at", null)
        .order("name", { ascending: true }),
    ]);

    if (payablesResult.error) {
      return NextResponse.json({ error: "Falha ao carregar contas vinculadas das compras." }, { status: 500 });
    }
    if (itemsResult.error) {
      return NextResponse.json({ error: "Falha ao carregar itens das compras do fornecedor." }, { status: 500 });
    }
    if (materialsResult.error) {
      return NextResponse.json({ error: "Falha ao carregar materiais do fornecedor." }, { status: 500 });
    }

    payableMap = new Map(
      (payablesResult.data ?? [])
        .filter((item) => typeof item.purchase_order_id === "string" && typeof item.id === "string")
        .map((item) => [
          String(item.purchase_order_id),
          {
            id: String(item.id),
            amount: Number(item.amount || 0),
            dueDate: typeof item.due_date === "string" ? item.due_date : null,
            paymentDate: typeof item.payment_date === "string" ? item.payment_date : null,
            status: String(item.status || "PENDING"),
            description: String(item.description || ""),
            supplierId: typeof item.supplier_id === "string" ? item.supplier_id : null,
            supplierName: typeof item.supplier_name === "string" ? item.supplier_name : null,
          },
        ])
    );

    const itemsByPurchase = new Map<
      string,
      Array<{
        id: string;
        materialId: string | null;
        materialName: string;
        unit: string;
        quantity: number;
        /* Quanto ja chegou. Menor que `quantity` = ainda falta receber. */
        receivedQuantity: number;
        unitPrice: number;
        totalPrice: number;
        /* O custo que entrou no estoque, com frete e imposto rateados. Nulo
           ate a mercadoria chegar. */
        landedUnitPrice: number | null;
        previousUnitPrice: number | null;
        priceChange: number | null;
        priceChangePercent: number | null;
        notes: string | null;
        createdAt: string | null;
      }>
    >();

    for (const row of itemsResult.data ?? []) {
      const purchaseOrderId = typeof row.purchase_order_id === "string" ? row.purchase_order_id : "";
      if (!purchaseOrderId) continue;
      const current = itemsByPurchase.get(purchaseOrderId) ?? [];
      current.push({
        id: String(row.id),
        materialId: typeof row.material_id === "string" ? row.material_id : null,
        materialName: String(row.material_name || ""),
        unit: String(row.unit || "un"),
        quantity: Number(row.quantity || 0),
        receivedQuantity: Number(row.received_quantity || 0),
        unitPrice: Number(row.unit_price || 0),
        totalPrice: Number(row.total_price || 0),
        landedUnitPrice:
          row.landed_unit_price === null || row.landed_unit_price === undefined ? null : Number(row.landed_unit_price),
        previousUnitPrice:
          row.previous_unit_price === null || row.previous_unit_price === undefined ? null : Number(row.previous_unit_price),
        priceChange: row.price_change === null || row.price_change === undefined ? null : Number(row.price_change),
        priceChangePercent:
          row.price_change_percent === null || row.price_change_percent === undefined
            ? null
            : Number(row.price_change_percent),
        notes: typeof row.notes === "string" ? row.notes : null,
        createdAt: typeof row.created_at === "string" ? row.created_at : null,
      });
      itemsByPurchase.set(purchaseOrderId, current);
    }

    const latestMaterialSnapshot = new Map<
      string,
      {
        materialId: string | null;
        materialName: string;
        unit: string;
        unitPrice: number;
        previousUnitPrice: number | null;
        priceChange: number | null;
        priceChangePercent: number | null;
        createdAt: string | null;
      }
    >();

    for (const row of itemsResult.data ?? []) {
      const key =
        typeof row.material_id === "string" && row.material_id
          ? `id:${row.material_id}`
          : `name:${String(row.material_name || "").trim().toLowerCase()}`;
      const createdAt = typeof row.created_at === "string" ? row.created_at : "";
      const current = latestMaterialSnapshot.get(key);
      if (!current || createdAt > String(current.createdAt || "")) {
        latestMaterialSnapshot.set(key, {
          materialId: typeof row.material_id === "string" ? row.material_id : null,
          materialName: String(row.material_name || ""),
          unit: String(row.unit || "un"),
          unitPrice: Number(row.unit_price || 0),
          previousUnitPrice:
            row.previous_unit_price === null || row.previous_unit_price === undefined ? null : Number(row.previous_unit_price),
          priceChange: row.price_change === null || row.price_change === undefined ? null : Number(row.price_change),
          priceChangePercent:
            row.price_change_percent === null || row.price_change_percent === undefined
              ? null
              : Number(row.price_change_percent),
          createdAt: typeof row.created_at === "string" ? row.created_at : null,
        });
      }
    }

    return NextResponse.json({
      ok: true,
      supplier: { id: String(supplier.id), name: String(supplier.name || "") },
      materials: (materialsResult.data ?? []).map((material) => {
        const key = `id:${String(material.id)}`;
        const snapshot = latestMaterialSnapshot.get(key) ?? null;
        return {
          id: String(material.id),
          name: String(material.name || ""),
          unit: String(material.unit || "un"),
          notes: typeof material.notes === "string" ? material.notes : null,
          sku: typeof material.sku === "string" ? material.sku : null,
          lastUnitPrice:
            snapshot?.unitPrice ??
            (material.last_unit_price === null || material.last_unit_price === undefined
              ? 0
              : Number(material.last_unit_price)),
          previousUnitPrice: snapshot?.previousUnitPrice ?? null,
          priceChange: snapshot?.priceChange ?? null,
          priceChangePercent: snapshot?.priceChangePercent ?? null,
          lastPurchaseAt: snapshot?.createdAt ?? (typeof material.last_purchase_at === "string" ? material.last_purchase_at : null),
        };
      }),
      purchases: (data ?? []).map((item) => {
        /* A versao entra na assinatura: gerar um link novo sobe a versao na
           compra e derruba na hora todos os enderecos ja enviados. */
        const publicToken = createSupplierPurchaseOrderPublicToken({
          purchaseOrderId: String(item.id),
          linkVersion: Number(item.public_link_version ?? 1),
        });
        const frete = Number(item.shipping_amount || 0);
        const desconto = Number(item.discount_amount || 0);
        const imposto = Number(item.tax_amount || 0);
        const total = Number(item.total_amount || 0);
        return {
          id: String(item.id),
          orderNumber: item.order_number,
          description: String(item.description || ""),
          totalAmount: total,
          shippingAmount: frete,
          discountAmount: desconto,
          taxAmount: imposto,
          extrasNoCusto: item.extras_no_custo !== false,
          /* O valor dos itens sai do total menos os extras, e nao da soma das
             linhas: compra registrada so com valor total, sem itens, tambem
             precisa de um subtotal para a tela mostrar a conta fechando. */
          itemsAmount: roundCurrency(total - frete - imposto + desconto),
          status: String(item.status || "draft"),
          orderedAt: item.ordered_at,
          expectedAt: item.expected_at,
          notes: item.notes ?? null,
          createdAt: item.created_at,
          updatedAt: item.updated_at,
          payable: payableMap.get(String(item.id)) ?? null,
          publicUrl: buildSupplierPurchaseOrderPublicUrl(origin, publicToken),
          pdfUrl: buildSupplierPurchaseOrderPublicUrl(origin, publicToken, { print: true }),
          publicLinkVersion: Number(item.public_link_version ?? 1),
          publicLinkExpiraEm: new Date(Date.now() + DIAS_DE_VALIDADE_DO_LINK * 24 * 60 * 60 * 1000).toISOString(),
          items: itemsByPurchase.get(String(item.id)) ?? [],
        };
      }),
    });
  } catch (error) {
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Falha inesperada ao carregar compras do fornecedor." },
      { status: 500 }
    );
  }
}

export async function POST(req: Request, { params }: Params) {
  const auth = await getAuth("suppliers.list.write");
  if (!auth.ok || !auth.ctx) {
    return NextResponse.json({ error: auth.status === 401 ? "Não autenticado." : "Sem permissão." }, { status: auth.status });
  }

  const { id } = await params;
  const body = (await req.json().catch(() => null)) as
    | {
        description?: unknown;
        totalAmount?: unknown;
        shippingAmount?: unknown;
        discountAmount?: unknown;
        taxAmount?: unknown;
        extrasNoCusto?: unknown;
        status?: unknown;
        orderedAt?: unknown;
        expectedAt?: unknown;
        notes?: unknown;
        items?: Array<{
          materialId?: unknown;
          name?: unknown;
          unit?: unknown;
          quantity?: unknown;
          unitPrice?: unknown;
          notes?: unknown;
        }> | null;
        createPayable?: unknown;
        payable?: {
          dueDate?: unknown;
          bankAccountId?: unknown;
          categoryId?: unknown;
          notes?: unknown;
          description?: unknown;
        } | null;
      }
    | null;

  const admin = createAdminClient();
  const { data: supplier } = await admin
    .from("suppliers")
    .select("id,name")
    .eq("company_id", auth.ctx.companyId)
    .eq("id", id)
    .is("deleted_at", null)
    .maybeSingle();

  if (!supplier?.id) return NextResponse.json({ error: "Fornecedor não encontrado." }, { status: 404 });

  const description = normalizeString(body?.description);
  const bodyTotalAmount = normalizeAmount(body?.totalAmount);
  const frete = valorExtra(body?.shippingAmount);
  const desconto = valorExtra(body?.discountAmount);
  const imposto = valorExtra(body?.taxAmount);
  /* Padrao ligado: frete e imposto SAO custo de aquisicao. Desligar e a
     excecao, para imposto que a empresa recupera. */
  const extrasNoCusto = body?.extrasNoCusto !== false;
  const status = normalizePurchaseStatus(body?.status);
  const orderedAt = normalizeString(body?.orderedAt) || null;
  const expectedAt = normalizeString(body?.expectedAt) || null;
  const notes = normalizeString(body?.notes) || null;
  const rawItems = Array.isArray(body?.items) ? body.items : [];
  /**
   * Rascunho e cancelada nao geram conta a pagar.
   *
   * Nada conferia o status aqui: marcar "Cancelado" com a chavinha ligada
   * criava uma compra cancelada com uma conta PENDENTE no financeiro, e em
   * producao 2 dos 3 rascunhos ja tinham conta. A conta nasce quando o pedido
   * sai para o fornecedor, nao antes.
   */
  const statusGeraConta = status !== "draft" && status !== "canceled";
  const createPayable = body?.createPayable === true && statusGeraConta;
  let payableInput:
    | {
        dueDate: string;
        bankAccountId: string;
        notes: string | null;
        description: string;
        categoryId: string | null;
      }
    | null = null;

  const normalizedItems = rawItems
    .map((item) => ({
      materialId: normalizeString(item?.materialId) || null,
      name: normalizeString(item?.name),
      unit: normalizeString(item?.unit) || "un",
      quantity: normalizeAmount(item?.quantity),
      unitPrice: normalizeAmount(item?.unitPrice),
      notes: normalizeString(item?.notes) || null,
    }))
    .filter((item) => item.materialId || item.name || Number.isFinite(item.quantity) || Number.isFinite(item.unitPrice));

  if (!description) return NextResponse.json({ error: "Descrição é obrigatória." }, { status: 400 });
  if (!Number.isFinite(frete) || frete < 0) {
    return NextResponse.json({ error: "Valor de frete inválido." }, { status: 400 });
  }
  if (!Number.isFinite(imposto) || imposto < 0) {
    return NextResponse.json({ error: "Valor de imposto inválido." }, { status: 400 });
  }
  if (!Number.isFinite(desconto) || desconto < 0) {
    return NextResponse.json({ error: "Valor de desconto inválido." }, { status: 400 });
  }
  if (orderedAt && !isIsoDate(orderedAt)) {
    return NextResponse.json({ error: "Data do pedido inválida." }, { status: 400 });
  }
  if (expectedAt && !isIsoDate(expectedAt)) {
    return NextResponse.json({ error: "Data prevista inválida." }, { status: 400 });
  }

  const preparedItems: Array<{
    materialId: string;
    materialName: string;
    unit: string;
    quantity: number;
    unitPrice: number;
    totalPrice: number;
    previousUnitPrice: number | null;
    priceChange: number | null;
    priceChangePercent: number | null;
    notes: string | null;
  }> = [];

  for (const item of normalizedItems) {
    if (!Number.isFinite(item.quantity) || item.quantity <= 0) {
      return NextResponse.json({ error: `Quantidade inválida para o item "${item.name || "sem nome"}".` }, { status: 400 });
    }
    if (!Number.isFinite(item.unitPrice) || item.unitPrice < 0) {
      return NextResponse.json({ error: `Valor unitário inválido para o item "${item.name || "sem nome"}".` }, { status: 400 });
    }

    let material;
    try {
      material = await resolveSupplierMaterial(admin, auth.ctx.companyId, id, auth.user?.id ?? null, item);
    } catch (materialError) {
      return NextResponse.json(
        { error: materialError instanceof Error ? materialError.message : "Falha ao processar material do fornecedor." },
        { status: 400 }
      );
    }

    const quantity = roundPrice(item.quantity);
    const unitPrice = roundPrice(item.unitPrice);
    const previousUnitPrice =
      material.lastUnitPrice !== null && Number.isFinite(material.lastUnitPrice) && material.lastUnitPrice > 0
        ? roundPrice(material.lastUnitPrice)
        : null;
    const totalPrice = roundCurrency(quantity * unitPrice);
    const priceChange = previousUnitPrice === null ? null : roundPrice(unitPrice - previousUnitPrice);
    const priceChangePercent =
      previousUnitPrice && previousUnitPrice > 0
        ? roundCurrency(((unitPrice - previousUnitPrice) / previousUnitPrice) * 100)
        : null;

    preparedItems.push({
      materialId: material.id,
      materialName: material.name,
      unit: item.unit || material.unit || "un",
      quantity,
      unitPrice,
      totalPrice,
      previousUnitPrice,
      priceChange,
      priceChangePercent,
      notes: item.notes,
    });
  }

  const computedItemsTotal = roundCurrency(preparedItems.reduce((sum, item) => sum + item.totalPrice, 0));
  /* `totalAmount` do corpo e o valor DOS ITENS quando a compra e registrada
     sem linhas, e nao o total fechado: frete, imposto e desconto entram por
     fora nos dois casos. */
  const itemsAmount = preparedItems.length > 0 ? computedItemsTotal : bodyTotalAmount;

  if (!Number.isFinite(itemsAmount) || itemsAmount < 0) {
    return NextResponse.json({ error: "Valor dos itens inválido." }, { status: 400 });
  }

  const totalAmount = roundCurrency(itemsAmount + frete + imposto - desconto);

  if (desconto > roundCurrency(itemsAmount + frete + imposto)) {
    return NextResponse.json({ error: "O desconto é maior do que a compra inteira." }, { status: 400 });
  }

  if (!Number.isFinite(totalAmount) || totalAmount <= 0) {
    return NextResponse.json({ error: "Valor total inválido." }, { status: 400 });
  }

  if (createPayable) {
    const dueDate = normalizeString(body?.payable?.dueDate);
    const bankAccountId = normalizeString(body?.payable?.bankAccountId);
    const payableNotes = normalizeString(body?.payable?.notes) || notes;
    const payableDescription = normalizeString(body?.payable?.description) || `Compra - ${description}`;

    if (!isIsoDate(dueDate)) {
      return NextResponse.json({ error: "Data de vencimento da conta é obrigatória." }, { status: 400 });
    }

    if (!bankAccountId) {
      return NextResponse.json({ error: "Conta bancária é obrigatória para gerar a conta a pagar." }, { status: 400 });
    }

    const { data: bankAccount, error: bankAccountError } = await admin
      .from("bank_accounts")
      .select("id")
      .eq("company_id", auth.ctx.companyId)
      .eq("id", bankAccountId)
      .eq("active", true)
      .maybeSingle();

    if (bankAccountError) {
      return NextResponse.json({ error: "Falha ao validar a conta bancária da compra." }, { status: 500 });
    }

    if (!bankAccount?.id) {
      return NextResponse.json({ error: "Conta bancária inválida para gerar a conta a pagar." }, { status: 400 });
    }

    let categoryId: string | null = null;
    try {
      categoryId = await resolveCategoryId(admin, auth.ctx.companyId, body?.payable?.categoryId);
    } catch (categoryError) {
      return NextResponse.json(
        { error: categoryError instanceof Error ? categoryError.message : "Falha ao processar categoria financeira." },
        { status: 400 }
      );
    }

    payableInput = {
      dueDate,
      bankAccountId,
      notes: payableNotes,
      description: payableDescription,
      categoryId,
    };
  }

  const { data, error } = await admin
    .from("supplier_purchase_orders")
    .insert({
      company_id: auth.ctx.companyId,
      supplier_id: id,
      description,
      total_amount: totalAmount,
      shipping_amount: frete,
      discount_amount: desconto,
      tax_amount: imposto,
      extras_no_custo: extrasNoCusto,
      status,
      ordered_at: orderedAt,
      expected_at: expectedAt,
      notes,
      created_by: auth.user?.id ?? null,
    })
    .select("id")
    .single();

  if (error || !data?.id) return NextResponse.json({ error: "Falha ao registrar compra." }, { status: 500 });

  const precosParaGravar: Array<{ materialId: string; unit: string; unitPrice: number; purchaseDate: string }> = [];

  if (preparedItems.length > 0) {
    const { error: itemsError } = await admin.from("supplier_purchase_order_items").insert(
      preparedItems.map((item) => ({
        company_id: auth.ctx.companyId,
        purchase_order_id: data.id,
        supplier_id: id,
        material_id: item.materialId,
        material_name: item.materialName,
        unit: item.unit,
        quantity: item.quantity,
        unit_price: item.unitPrice,
        total_price: item.totalPrice,
        previous_unit_price: item.previousUnitPrice,
        price_change: item.priceChange,
        price_change_percent: item.priceChangePercent,
        notes: item.notes,
      }))
    );

    if (itemsError) {
      await admin.from("supplier_purchase_orders").delete().eq("company_id", auth.ctx.companyId).eq("id", data.id);
      return NextResponse.json({ error: "Falha ao registrar itens da compra." }, { status: 500 });
    }

    for (const item of preparedItems) {
      /* Guardado para gravar depois. Antes o preco era escrito aqui: se a
         conta a pagar falhasse logo abaixo, a compra era apagada mas o
         "ultimo preco" do material ficava alterado para sempre, apontando
         para uma compra que nunca existiu. */
      precosParaGravar.push({
        materialId: item.materialId,
        unit: item.unit,
        unitPrice: item.unitPrice,
        purchaseDate: orderedAt || new Date().toISOString().slice(0, 10),
      });
    }
  }

  let payableId: string | null = null;

  if (createPayable && payableInput) {
    const { data: payable, error: payableError } = await insertAccountsPayableWithFallback(admin, {
      company_id: auth.ctx.companyId,
      description: payableInput.description,
      amount: totalAmount,
      due_date: payableInput.dueDate,
      bank_account_id: payableInput.bankAccountId,
      category_id: payableInput.categoryId,
      supplier_id: id,
      supplier_name: typeof supplier.name === "string" ? supplier.name : null,
      purchase_order_id: data.id,
      notes: payableInput.notes,
      status: "PENDING",
      payment_date: null,
    });

    if (payableError || !payable?.id) {
      await admin.from("supplier_purchase_orders").delete().eq("company_id", auth.ctx.companyId).eq("id", data.id);
      return NextResponse.json(
        {
          error:
            payableError?.message ||
            payableError?.details ||
            payableError?.hint ||
            "Falha ao gerar conta a pagar da compra.",
        },
        { status: 500 }
      );
    }

    payableId = String(payable.id);

    await insertActivity(
      admin,
      auth.ctx.companyId,
      id,
      auth.user?.id ?? null,
      payableId,
      "payable.created_from_purchase",
      `Conta a pagar criada a partir de uma compra de ${supplier.name || "fornecedor"}.`,
      { purchaseOrderId: data.id, amount: totalAmount, dueDate: payableInput.dueDate }
    );
  }

  /* Agora sim: a compra existe e a conta a pagar tambem. So aqui o historico
     de preco do material pode mudar. */
  for (const preco of precosParaGravar) {
    await admin
      .from("supplier_materials")
      .update({
        unit: preco.unit,
        last_unit_price: preco.unitPrice,
        last_purchase_at: preco.purchaseDate,
      })
      .eq("company_id", auth.ctx.companyId)
      .eq("supplier_id", id)
      .eq("id", preco.materialId);
  }

  await insertActivity(
    admin,
    auth.ctx.companyId,
    id,
    auth.user?.id ?? null,
    data.id as string,
    "purchase.created",
    `Compra registrada para ${supplier.name || "fornecedor"}.`,
    { description, totalAmount, payableId, itemsCount: preparedItems.length }
  );

  return NextResponse.json({ ok: true, id: data.id, payableId });
}

/**
 * O ciclo de vida do pedido de compra.
 *
 * Nao existia: a rota tinha GET, POST e DELETE. O status era escolhido na hora
 * de cadastrar e congelava ali, entao "Recebimento parcial", "Recebido" e
 * "Cancelado" so eram alcancaveis declarando isso na criacao, o que nao faz
 * sentido, e para marcar como recebido depois a pessoa tinha que apagar a
 * compra e refazer. Em producao, das 22 compras, nunca houve uma parcial nem
 * uma cancelada.
 */
const TRANSICOES: Record<PurchaseStatus, PurchaseStatus[]> = {
  draft: ["ordered", "canceled"],
  ordered: ["partial", "received", "canceled"],
  partial: ["received", "canceled"],
  /* Recebido e cancelado sao fim de linha. Desfazer um recebimento mexeria em
     estoque e custo ja aplicados; para isso existe o botao de excluir, que
     confere a conta a pagar antes. */
  received: [],
  canceled: [],
};

const ROTULO_DO_STATUS: Record<PurchaseStatus, string> = {
  draft: "Rascunho",
  ordered: "Pedido emitido",
  partial: "Recebimento parcial",
  received: "Recebido",
  canceled: "Cancelado",
};

export async function PATCH(req: Request, { params }: Params) {
  const auth = await getAuth("suppliers.list.write");
  if (!auth.ok || !auth.ctx) {
    return NextResponse.json({ error: auth.status === 401 ? "Nao autenticado." : "Sem permissao." }, { status: auth.status });
  }

  const { id } = await params;
  const body = (await req.json().catch(() => null)) as
    | { purchaseOrderId?: unknown; acao?: unknown; status?: unknown }
    | null;

  const purchaseOrderId = normalizeString(body?.purchaseOrderId);
  if (!purchaseOrderId) {
    return NextResponse.json({ error: "Compra invalida." }, { status: 400 });
  }

  const admin = createAdminClient();

  const [{ data: supplier }, { data: purchase, error: purchaseError }] = await Promise.all([
    admin.from("suppliers").select("id,name").eq("company_id", auth.ctx.companyId).eq("id", id).is("deleted_at", null).maybeSingle(),
    admin
      .from("supplier_purchase_orders")
      .select("id,description,status,total_amount,public_link_version")
      .eq("company_id", auth.ctx.companyId)
      .eq("supplier_id", id)
      .eq("id", purchaseOrderId)
      .maybeSingle(),
  ]);

  if (!supplier?.id) return NextResponse.json({ error: "Fornecedor nao encontrado." }, { status: 404 });
  if (purchaseError) return NextResponse.json({ error: "Falha ao localizar compra." }, { status: 500 });
  if (!purchase?.id) return NextResponse.json({ error: "Compra nao encontrada." }, { status: 404 });

  /* ------------------------------------------------------------------
     Gerar um link novo: sobe a versao e derruba os enderecos ja enviados.
     ------------------------------------------------------------------ */
  if (normalizeString(body?.acao) === "novo-link") {
    const versaoNova = Math.max(1, Number(purchase.public_link_version ?? 1)) + 1;

    const { error: versaoError } = await admin
      .from("supplier_purchase_orders")
      .update({ public_link_version: versaoNova })
      .eq("company_id", auth.ctx.companyId)
      .eq("id", purchaseOrderId);

    if (versaoError) {
      return NextResponse.json({ error: "Falha ao gerar um link novo." }, { status: 500 });
    }

    const origin = origemPublicaDoAdmin(req);
    const token = createSupplierPurchaseOrderPublicToken({
      purchaseOrderId,
      linkVersion: versaoNova,
    });

    await insertActivity(
      admin,
      auth.ctx.companyId,
      id,
      auth.user?.id ?? null,
      purchaseOrderId,
      "purchase.link_reissued",
      `Link do pedido "${purchase.description || "compra"}" foi trocado. O anterior parou de abrir.`,
      { versao: versaoNova }
    );

    return NextResponse.json({
      ok: true,
      publicUrl: buildSupplierPurchaseOrderPublicUrl(origin, token),
      pdfUrl: buildSupplierPurchaseOrderPublicUrl(origin, token, { print: true }),
      publicLinkVersion: versaoNova,
      publicLinkExpiraEm: new Date(Date.now() + DIAS_DE_VALIDADE_DO_LINK * 24 * 60 * 60 * 1000).toISOString(),
    });
  }

  /* ------------------------------------------------------------------
     Andar com o status.
     ------------------------------------------------------------------ */
  const statusAtual = normalizePurchaseStatus(purchase.status);
  const statusNovo = normalizePurchaseStatus(body?.status);

  if (!body || typeof body.status !== "string") {
    return NextResponse.json({ error: "Informe o novo status da compra." }, { status: 400 });
  }

  if (statusNovo === statusAtual) {
    return NextResponse.json({ ok: true, status: statusAtual });
  }

  if (!TRANSICOES[statusAtual].includes(statusNovo)) {
    return NextResponse.json(
      {
        error: `Uma compra em "${ROTULO_DO_STATUS[statusAtual]}" nao pode virar "${ROTULO_DO_STATUS[statusNovo]}".`,
      },
      { status: 400 }
    );
  }

  /* Cancelar com conta ja baixada mexeria no caixa fechado. Mesma regra que a
     exclusao ja aplicava. */
  if (statusNovo === "canceled") {
    const { data: contas } = await admin
      .from("accounts_payable")
      .select("id,status")
      .eq("company_id", auth.ctx.companyId)
      .eq("purchase_order_id", purchaseOrderId);

    const paga = (contas ?? []).find((conta) => String(conta.status || "").toUpperCase() === "PAID");
    if (paga?.id) {
      return NextResponse.json(
        { error: "Essa compra tem conta a pagar ja baixada. Ajuste o financeiro antes de cancelar." },
        { status: 400 }
      );
    }

    /* A conta que ainda nao foi paga vai junto: cobrar por uma compra
       cancelada e o problema que a gente esta consertando. */
    const idsEmAberto = (contas ?? []).map((conta) => String(conta.id)).filter(Boolean);
    if (idsEmAberto.length > 0) {
      await admin
        .from("accounts_payable")
        .update({ status: "CANCELED" })
        .eq("company_id", auth.ctx.companyId)
        .in("id", idsEmAberto);
    }
  }

  const { error: statusError } = await admin
    .from("supplier_purchase_orders")
    .update({ status: statusNovo })
    .eq("company_id", auth.ctx.companyId)
    .eq("supplier_id", id)
    .eq("id", purchaseOrderId);

  if (statusError) {
    return NextResponse.json({ error: "Falha ao mudar o status da compra." }, { status: 500 });
  }

  await insertActivity(
    admin,
    auth.ctx.companyId,
    id,
    auth.user?.id ?? null,
    purchaseOrderId,
    "purchase.status_changed",
    `Compra "${purchase.description || "sem descricao"}" passou de ${ROTULO_DO_STATUS[statusAtual]} para ${ROTULO_DO_STATUS[statusNovo]}.`,
    { de: statusAtual, para: statusNovo }
  );

  return NextResponse.json({ ok: true, status: statusNovo });
}

export async function DELETE(req: Request, { params }: Params) {
  const auth = await getAuth("suppliers.list.write");
  if (!auth.ok || !auth.ctx) {
    return NextResponse.json({ error: auth.status === 401 ? "Não autenticado." : "Sem permissão." }, { status: auth.status });
  }

  const { id } = await params;
  const body = (await req.json().catch(() => null)) as { purchaseOrderId?: unknown } | null;
  const purchaseOrderId = normalizeString(body?.purchaseOrderId);
  if (!purchaseOrderId) {
    return NextResponse.json({ error: "Compra inválida." }, { status: 400 });
  }

  const admin = createAdminClient();

  const [{ data: supplier }, { data: purchase, error: purchaseError }] = await Promise.all([
    admin.from("suppliers").select("id,name").eq("company_id", auth.ctx.companyId).eq("id", id).is("deleted_at", null).maybeSingle(),
    admin
      .from("supplier_purchase_orders")
      .select("id,description,total_amount,status")
      .eq("company_id", auth.ctx.companyId)
      .eq("supplier_id", id)
      .eq("id", purchaseOrderId)
      .maybeSingle(),
  ]);

  if (!supplier?.id) return NextResponse.json({ error: "Fornecedor não encontrado." }, { status: 404 });
  if (purchaseError) return NextResponse.json({ error: "Falha ao localizar compra." }, { status: 500 });
  if (!purchase?.id) return NextResponse.json({ error: "Compra não encontrada." }, { status: 404 });

  const { data: linkedPayables, error: linkedPayablesError } = await admin
    .from("accounts_payable")
    .select("id,status,description")
    .eq("company_id", auth.ctx.companyId)
    .eq("purchase_order_id", purchaseOrderId);

  if (linkedPayablesError) {
    return NextResponse.json({ error: "Falha ao localizar conta vinculada à compra." }, { status: 500 });
  }

  const paidLinked = (linkedPayables ?? []).find((item) => String(item.status || "").toUpperCase() === "PAID");
  if (paidLinked?.id) {
    return NextResponse.json(
      { error: "Esta compra possui uma conta a pagar já baixada. Desfaça ou ajuste o financeiro antes de excluir." },
      { status: 400 }
    );
  }

  if ((linkedPayables ?? []).length > 0) {
    const linkedIds = linkedPayables
      .map((item) => (typeof item.id === "string" ? item.id : ""))
      .filter(Boolean);

    if (linkedIds.length > 0) {
      const { error: deletePayablesError } = await admin
        .from("accounts_payable")
        .delete()
        .eq("company_id", auth.ctx.companyId)
        .in("id", linkedIds);

      if (deletePayablesError) {
        return NextResponse.json({ error: "Falha ao excluir a conta a pagar vinculada." }, { status: 500 });
      }
    }
  }

  const { error: deletePurchaseError } = await admin
    .from("supplier_purchase_orders")
    .delete()
    .eq("company_id", auth.ctx.companyId)
    .eq("supplier_id", id)
    .eq("id", purchaseOrderId);

  if (deletePurchaseError) {
    return NextResponse.json({ error: "Falha ao excluir compra." }, { status: 500 });
  }

  await insertActivity(
    admin,
    auth.ctx.companyId,
    id,
    auth.user?.id ?? null,
    purchaseOrderId,
    "purchase.deleted",
    `Compra removida de ${supplier.name || "fornecedor"}.`,
    {
      description: String(purchase.description || ""),
      totalAmount: Number(purchase.total_amount || 0),
      removedLinkedPayables: (linkedPayables ?? []).map((item) => item.id),
    }
  );

  return NextResponse.json({ ok: true });
}
