import type { SupabaseClient, User } from "@supabase/supabase-js";
import { canCompany, getCompanyContext, type CompanyContext } from "@/lib/company-context";
import { ApiV1Error } from "@/lib/api-v1/errors";
import { avisarLoja } from "@/lib/loja/avisar-loja";

const writableFields = new Set(["name", "slug", "pricing_rule", "is_visible", "parent_id"]);

function pickWritablePatch(value: unknown) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
  const record = value as Record<string, unknown>;
  const patch: Record<string, unknown> = {};
  for (const [key, fieldValue] of Object.entries(record)) {
    if (writableFields.has(key)) patch[key] = fieldValue;
  }
  return patch;
}

function slugify(input: string) {
  return input
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^\w\s-]/g, "")
    .trim()
    .replace(/\s+/g, "-");
}

function parseParentId(value: unknown) {
  if (value === null || value === undefined || value === "") return { parentId: null as string | null, error: null as string | null };
  if (typeof value !== "string") return { parentId: null as string | null, error: "Categoria pai invalida." };
  const trimmed = value.trim();
  if (!trimmed) return { parentId: null as string | null, error: null as string | null };
  return { parentId: trimmed, error: null as string | null };
}

async function ensureParentCategory(params: {
  supabase: SupabaseClient;
  tenantIds: string[];
  categoryId: string;
  parentId: string | null;
}) {
  if (!params.parentId) return { parent: null as { id: string; user_id: string; parent_id: string | null } | null, error: null as string | null };
  if (params.parentId === params.categoryId) {
    return { parent: null as { id: string; user_id: string; parent_id: string | null } | null, error: "Categoria nao pode ser pai dela mesma." };
  }

  const { data: parent, error } = await params.supabase
    .from("categories")
    .select("id,user_id,parent_id")
    .eq("id", params.parentId)
    .in("user_id", params.tenantIds)
    .maybeSingle();

  if (error) return { parent: null as { id: string; user_id: string; parent_id: string | null } | null, error: error.message };
  if (!parent) {
    return { parent: null as { id: string; user_id: string; parent_id: string | null } | null, error: "Categoria pai nao encontrada." };
  }

  return {
    parent: {
      id: parent.id as string,
      user_id: parent.user_id as string,
      parent_id: parent.parent_id ? String(parent.parent_id) : null,
    },
    error: null as string | null,
  };
}

async function validateCategoryCycle(params: {
  supabase: SupabaseClient;
  tenantIds: string[];
  categoryId: string;
  parentId: string | null;
}) {
  if (!params.parentId) return { error: null as string | null };
  const visited = new Set<string>();
  let currentId: string | null = params.parentId;

  while (currentId) {
    if (currentId === params.categoryId) {
      return { error: "Hierarquia invalida: esta categoria esta dentro da cadeia da categoria pai." };
    }
    if (visited.has(currentId)) {
      return { error: "Hierarquia invalida: ciclo detectado." };
    }
    visited.add(currentId);

    const result = (await params.supabase
      .from("categories")
      .select("id,parent_id")
      .eq("id", currentId)
      .in("user_id", params.tenantIds)
      .maybeSingle()) as unknown as {
      data: { parent_id?: unknown } | null;
      error: { message: string } | null;
    };

    if (result.error) return { error: result.error.message };
    if (!result.data) break;
    currentId = result.data.parent_id ? String(result.data.parent_id) : null;
  }

  return { error: null as string | null };
}

export async function resolveCategoriesAuth(supabase: SupabaseClient, user: User) {
  const ctx = await getCompanyContext(supabase, user);
  return {
    ctx,
    tenantIds: ctx.tenantIds?.length ? ctx.tenantIds : [user.id],
  };
}

export async function listCategories(params: { supabase: SupabaseClient; user: User }) {
  const { tenantIds } = await resolveCategoriesAuth(params.supabase, params.user);
  const { data, error } = await params.supabase
    .from("categories")
    .select("id,name,slug,pricing_rule,is_visible,parent_id")
    .in("user_id", tenantIds)
    .order("name", { ascending: true });

  if (error) throw new ApiV1Error(400, "categories_fetch_failed", error.message);
  return { ok: true, categories: data ?? [] };
}

export async function getCategoryById(params: { supabase: SupabaseClient; user: User; id: string }) {
  const { tenantIds } = await resolveCategoriesAuth(params.supabase, params.user);
  const { data, error } = await params.supabase
    .from("categories")
    .select("id,name,slug,pricing_rule,is_visible,parent_id")
    .eq("id", params.id)
    .in("user_id", tenantIds)
    .maybeSingle();

  if (error) throw new ApiV1Error(400, "category_fetch_failed", error.message);
  if (!data) throw new ApiV1Error(404, "category_not_found", "Categoria nao encontrada.");
  return { ok: true, category: data };
}

/* Categoria aparece no menu e nos filtros da loja, que guarda a vitrine por
   dois minutos: criar, editar e apagar avisam a loja (lib/loja/avisar-loja.ts).
   `request` é opcional e só serve para descobrir o endereço da loja. */
export async function createCategory(params: {
  supabase: SupabaseClient;
  user: User;
  body: Record<string, unknown> | null;
  request?: Request;
}) {
  const ctx = await getCompanyContext(params.supabase, params.user);
  if (!canCompany(ctx, params.user.id, "categories.write")) {
    throw new ApiV1Error(403, "forbidden", "Sem permissao.");
  }

  const patch = pickWritablePatch(params.body);
  const name = typeof patch.name === "string" ? patch.name.trim() : "";
  if (!name) throw new ApiV1Error(400, "invalid_name", "Nome e obrigatorio.");

  const slugRaw = typeof patch.slug === "string" && patch.slug.trim() ? patch.slug.trim() : slugify(name);
  const writeUserId = ctx.ownerId ?? ctx.companyId;
  const isVisible = typeof patch.is_visible === "boolean" ? patch.is_visible : true;
  const parsedParent = parseParentId(patch.parent_id);
  if (parsedParent.error) throw new ApiV1Error(400, "invalid_parent_category", parsedParent.error);

  const parentCheck = await ensureParentCategory({
    supabase: params.supabase,
    tenantIds: ctx.tenantIds?.length ? ctx.tenantIds : [params.user.id],
    categoryId: "",
    parentId: parsedParent.parentId,
  });

  if (parentCheck.error) throw new ApiV1Error(400, "invalid_parent_category", parentCheck.error);
  if (parentCheck.parent && parentCheck.parent.user_id !== writeUserId) {
    throw new ApiV1Error(400, "invalid_parent_category", "Categoria pai deve pertencer ao mesmo usuario.");
  }

  const { data, error } = await params.supabase
    .from("categories")
    .insert({
      ...patch,
      user_id: writeUserId,
      name,
      slug: slugRaw,
      is_visible: isVisible,
      parent_id: parsedParent.parentId,
      pricing_rule:
        patch.pricing_rule || { marginTargetPct: 50, marginMinPct: 35, rounding: { type: "END_WITH", cents: 90 } },
    })
    .select("id,name,slug,pricing_rule,is_visible,parent_id")
    .single();

  if (error) throw new ApiV1Error(400, "category_create_failed", error.message);
  avisarLoja(ctx.ownerId ?? params.user.id, params.request);
  return { ok: true, category: data };
}

export async function updateCategory(params: {
  supabase: SupabaseClient;
  user: User;
  id: string;
  patch: unknown;
  request?: Request;
}) {
  const ctx = await getCompanyContext(params.supabase, params.user);
  if (!canCompany(ctx, params.user.id, "categories.write")) {
    throw new ApiV1Error(403, "forbidden", "Sem permissao.");
  }

  const patch = pickWritablePatch(params.patch);
  if (!Object.keys(patch).length) {
    throw new ApiV1Error(400, "empty_patch", "Nada para atualizar.");
  }

  const tenantIds = ctx.tenantIds?.length ? ctx.tenantIds : [params.user.id];
  const { data: currentCategory, error: currentCategoryError } = await params.supabase
    .from("categories")
    .select("id,user_id,parent_id")
    .eq("id", params.id)
    .in("user_id", tenantIds)
    .maybeSingle();

  if (currentCategoryError) throw new ApiV1Error(400, "category_fetch_failed", currentCategoryError.message);
  if (!currentCategory) throw new ApiV1Error(404, "category_not_found", "Categoria nao encontrada.");

  let patchToSave = { ...patch };
  if (Object.prototype.hasOwnProperty.call(patch, "parent_id")) {
    const parsedParent = parseParentId(patch.parent_id);
    if (parsedParent.error) throw new ApiV1Error(400, "invalid_parent_category", parsedParent.error);

    const parentCheck = await ensureParentCategory({
      supabase: params.supabase,
      tenantIds,
      categoryId: params.id,
      parentId: parsedParent.parentId,
    });
    if (parentCheck.error) throw new ApiV1Error(400, "invalid_parent_category", parentCheck.error);
    if (parentCheck.parent && parentCheck.parent.user_id !== String(currentCategory.user_id)) {
      throw new ApiV1Error(400, "invalid_parent_category", "Categoria pai deve pertencer ao mesmo usuario.");
    }

    const cycleValidation = await validateCategoryCycle({
      supabase: params.supabase,
      tenantIds,
      categoryId: params.id,
      parentId: parsedParent.parentId,
    });
    if (cycleValidation.error) throw new ApiV1Error(400, "invalid_category_hierarchy", cycleValidation.error);
    patchToSave = { ...patchToSave, parent_id: parsedParent.parentId };
  }

  const { data, error } = await params.supabase
    .from("categories")
    .update(patchToSave)
    .eq("id", params.id)
    .in("user_id", tenantIds)
    .select("id")
    .maybeSingle();

  if (error) throw new ApiV1Error(400, "category_update_failed", error.message);
  if (!data) throw new ApiV1Error(404, "category_not_found", "Categoria nao encontrada.");
  avisarLoja(ctx.ownerId ?? params.user.id, params.request);
  return { ok: true };
}

export async function deleteCategory(params: { supabase: SupabaseClient; user: User; id: string; request?: Request }) {
  const ctx = await getCompanyContext(params.supabase, params.user);
  if (!canCompany(ctx, params.user.id, "categories.write")) {
    throw new ApiV1Error(403, "forbidden", "Sem permissao.");
  }

  const tenantIds = ctx.tenantIds?.length ? ctx.tenantIds : [params.user.id];
  const { data, error } = await params.supabase
    .from("categories")
    .delete()
    .eq("id", params.id)
    .in("user_id", tenantIds)
    .select("id")
    .maybeSingle();

  if (error) throw new ApiV1Error(400, "category_delete_failed", error.message);
  if (!data) throw new ApiV1Error(404, "category_not_found", "Categoria nao encontrada.");
  avisarLoja(ctx.ownerId ?? params.user.id, params.request);
  return { ok: true };
}

export type CategoriesAuthContext = {
  ctx: CompanyContext;
  tenantIds: string[];
};
