/**
 * سرویس کاتالوگ — خواندن محصولات، دسته‌ها و برندها از PostgreSQL با Prisma.
 * صفحات فقط از این توابع استفاده می‌کنند و مستقیماً به دیتابیس دسترسی ندارند.
 */
import "server-only";
import { cache } from "react";
import { db } from "@/lib/db";
import { normalizeFa } from "@/lib/persian";
import type { Prisma } from "@/generated/prisma/client";
import type { Banner, Brand, Category, Product, ProductShape, ProductSummary, ProductTag, Review } from "@/types/catalog";
import type { ListingFilters } from "@/lib/listing-params";

export const PAGE_SIZE = 12;

/** فیلدهای داخلی تنوع که نباید به صفحات عمومی برسند (قیمت دلاری محصولات «اورجینال») */
const PRIVATE_VARIANT_FIELDS = { priceUsdCents: true, compareAtUsdCents: true } as const;

// ── تبدیل ردیف دیتابیس به تایپ‌های نمایشی ─────────────────

const summaryInclude = {
  brand: { select: { name: true, nameEn: true, slug: true } },
  variants: { where: { isActive: true }, select: { price: true, compareAtPrice: true, stock: true, colorHex: true }, orderBy: { price: "asc" } },
  images: { select: { url: true, alt: true, variantId: true }, orderBy: [{ sortOrder: "asc" }, { id: "asc" }], take: 12 },
} satisfies Prisma.ProductInclude;

type SummaryRow = Prisma.ProductGetPayload<{ include: typeof summaryInclude }>;

/** عکس اصلی: اولین عکس عمومی، و اگر نبود اولین عکس؛ با variantId عکس مخصوص همان تنوع ترجیح داده می‌شود */
function pickImage(images: SummaryRow["images"], variantId?: string) {
  const img = (variantId && images.find((i) => i.variantId === variantId)) || images.find((i) => !i.variantId) || images[0];
  return img ? { url: img.url, alt: img.alt } : undefined;
}

export function toSummary(p: SummaryRow, variantId?: string): ProductSummary {
  const cheapest = p.variants[0];
  return {
    id: p.id,
    slug: p.slug,
    name: p.name,
    brand: p.brand,
    shape: p.shape as ProductShape,
    tone: p.tone,
    price: p.minPrice,
    compareAtPrice: cheapest?.compareAtPrice ?? undefined,
    rating: p.ratingAvg,
    reviewCount: p.reviewCount,
    tags: p.tags as ProductTag[],
    colorCount: p.variants.filter((v) => v.colorHex).length,
    inStock: p.variants.some((v) => v.stock > 0),
    image: pickImage(p.images, variantId),
  };
}

const noNull = <T,>(v: T | null) => v ?? undefined;

// ── دسته‌ها (کش کوتاه‌مدت چون به‌ندرت تغییر می‌کنند) ─────────

export interface CategoryIndex {
  all: Category[];
  byId: Map<string, Category>;
  tree: (Category & { children: Category[] })[];
  /** آدرس خوانای دسته: /c/والد/فرزند */
  href: (id: string) => string;
  /** مسیر از ریشه تا دسته */
  path: (id: string) => Category[];
  /** شناسه دسته و همه زیردسته‌هایش */
  descendants: (id: string) => string[];
}

let categoryCache: { at: number; index: Promise<CategoryIndex> } | undefined;
const CATEGORY_TTL_MS = 60_000;

async function loadCategoryIndex(): Promise<CategoryIndex> {
  const rows = await db.category.findMany({ where: { isActive: true }, orderBy: { sortOrder: "asc" } });
  const all: Category[] = rows.map((c) => ({ id: c.id, slug: c.slug, name: c.name, parentId: c.parentId, description: noNull(c.description) }));
  const byId = new Map(all.map((c) => [c.id, c]));
  const path = (id: string) => {
    const out: Category[] = [];
    for (let c = byId.get(id); c; c = c.parentId ? byId.get(c.parentId) : undefined) out.unshift(c);
    return out;
  };
  const descendants = (id: string): string[] => [id, ...all.filter((c) => c.parentId === id).flatMap((c) => descendants(c.id))];
  return {
    all,
    byId,
    tree: all.filter((c) => !c.parentId).map((r) => ({ ...r, children: all.filter((c) => c.parentId === r.id) })),
    href: (id) => `/c/${path(id).map((c) => c.slug).join("/")}`,
    path,
    descendants,
  };
}

export function getCategoryIndex(): Promise<CategoryIndex> {
  if (!categoryCache || Date.now() - categoryCache.at > CATEGORY_TTL_MS) {
    const index = loadCategoryIndex();
    categoryCache = { at: Date.now(), index };
    index.catch(() => (categoryCache = undefined)); // در صورت خطا دفعه بعد دوباره تلاش شود
  }
  return categoryCache.index;
}

// ── برندها و انواع پوست ────────────────────────────────────

export async function getBrands(): Promise<Brand[]> {
  return db.brand.findMany({ where: { isActive: true }, orderBy: { nameEn: "asc" }, omit: { isActive: true } });
}

export const getBrandBySlug = cache(async (slug: string): Promise<Brand | undefined> => {
  return noNull(await db.brand.findFirst({ where: { slug, isActive: true }, omit: { isActive: true } }));
});

export async function getSkinTypes() {
  return db.skinType.findMany({ orderBy: { id: "asc" } });
}

// ── لیست محصولات با فیلتر و مرتب‌سازی ─────────────────────

export interface ListingResult {
  items: ProductSummary[];
  total: number;
  page: number;
  pageCount: number;
  facets: {
    brands: { slug: string; name: string; count: number }[];
    skinTypes: { id: string; name: string; count: number }[];
    priceBounds: [number, number];
  };
}

/** شرط جستجو: همه کلمات باید در متن یکسان‌سازی‌شده محصول باشند */
function searchWhere(q: string): Prisma.ProductWhereInput {
  const tokens = normalizeFa(q).split(" ").filter(Boolean).slice(0, 6);
  return { AND: tokens.map((t) => ({ searchText: { contains: t } })) };
}

const sortOrders: Record<ListingFilters["sort"], Prisma.ProductOrderByWithRelationInput[]> = {
  popular: [{ soldCount: "desc" }, { id: "asc" }],
  newest: [{ createdAt: "desc" }, { id: "asc" }],
  "price-asc": [{ minPrice: "asc" }, { id: "asc" }],
  "price-desc": [{ minPrice: "desc" }, { id: "asc" }],
  rating: [{ ratingAvg: "desc" }, { reviewCount: "desc" }, { id: "asc" }],
};

export async function listProducts(filters: ListingFilters): Promise<ListingResult> {
  const cats = await getCategoryIndex();

  // ۱) محدوده پایه: دسته و عبارت جستجو (شمارنده‌های فیلتر روی همین محاسبه می‌شوند)
  const base: Prisma.ProductWhereInput = {
    isActive: true,
    ...(filters.categoryId && { categoryId: { in: cats.descendants(filters.categoryId) } }),
    ...(filters.q && searchWhere(filters.q)),
  };

  // ۲) فیلترهای کاربر
  const where: Prisma.ProductWhereInput = {
    AND: [
      base,
      filters.brands.length ? { brand: { slug: { in: filters.brands } } } : {},
      filters.skin.length ? { skinTypes: { some: { id: { in: filters.skin } } } } : {},
      filters.min !== undefined ? { minPrice: { gte: filters.min } } : {},
      filters.max !== undefined ? { minPrice: { lte: filters.max } } : {},
      filters.rating ? { ratingAvg: { gte: filters.rating } } : {},
      filters.inStock ? { variants: { some: { isActive: true, stock: { gt: 0 } } } } : {},
    ],
  };

  const total = await db.product.count({ where });
  const pageCount = Math.max(1, Math.ceil(total / PAGE_SIZE));
  const page = Math.min(filters.page, pageCount);

  const [rows, brandGroups, skinRows, bounds] = await Promise.all([
    db.product.findMany({ where, include: summaryInclude, orderBy: sortOrders[filters.sort], skip: (page - 1) * PAGE_SIZE, take: PAGE_SIZE }),
    db.product.groupBy({ by: ["brandId"], where: base, _count: { _all: true } }),
    db.skinType.findMany({ select: { id: true, name: true, _count: { select: { products: { where: base } } } }, orderBy: { id: "asc" } }),
    db.product.aggregate({ where: base, _min: { minPrice: true }, _max: { minPrice: true } }),
  ]);

  const brandRows = await db.brand.findMany({ where: { id: { in: brandGroups.map((g) => g.brandId) } }, orderBy: { nameEn: "asc" } });

  return {
    items: rows.map((r) => toSummary(r)),
    total,
    page,
    pageCount,
    facets: {
      brands: brandRows.map((b) => ({ slug: b.slug, name: b.name, count: brandGroups.find((g) => g.brandId === b.id)!._count._all })),
      skinTypes: skinRows.filter((s) => s._count.products > 0).map((s) => ({ id: s.id, name: s.name, count: s._count.products })),
      priceBounds: [bounds._min.minPrice ?? 0, bounds._max.minPrice ?? 0],
    },
  };
}

// ── محصول تکی ────────────────────────────────────────────

export const getProductById = cache(async (id: number) => {
  const row = await db.product.findFirst({
    where: { id, isActive: true },
    include: {
      brand: { omit: { isActive: true } },
      skinTypes: true,
      // قیمت دلاری («اورجینال») هرگز از سرویس عمومی خارج نمی‌شود
      variants: { where: { isActive: true }, orderBy: { sortOrder: "asc" }, omit: PRIVATE_VARIANT_FIELDS },
      images: { orderBy: [{ sortOrder: "asc" }, { id: "asc" }] },
    },
  });
  if (!row) return undefined;
  const cats = await getCategoryIndex();
  const product: Product = {
    id: row.id,
    slug: row.slug,
    name: row.name,
    brandId: row.brandId,
    categoryId: row.categoryId,
    skinTypeIds: row.skinTypes.map((s) => s.id),
    shape: row.shape as ProductShape,
    tone: row.tone,
    summary: row.summary,
    description: row.description,
    howToUse: row.howToUse,
    ingredients: row.ingredients,
    ircCode: row.ircCode,
    madeIn: row.madeIn,
    tags: row.tags as ProductTag[],
    variants: row.variants.map(toVariant),
    rating: row.ratingAvg,
    reviewCount: row.reviewCount,
    soldCount: row.soldCount,
    createdAt: row.createdAt.toISOString(),
    images: row.images.map((i) => ({ url: i.url, alt: i.alt, variantId: noNull(i.variantId) })),
  };
  return { product, brand: row.brand, categoryPath: cats.path(row.categoryId), skinTypes: row.skinTypes };
});

function toVariant(v: Prisma.ProductVariantGetPayload<{ omit: typeof PRIVATE_VARIANT_FIELDS }>) {
  return {
    id: v.id,
    sku: v.sku,
    colorName: noNull(v.colorName),
    colorHex: noNull(v.colorHex),
    size: noNull(v.size),
    price: v.price,
    compareAtPrice: noNull(v.compareAtPrice),
    stock: v.stock,
  };
}

export async function getProductReviews(productId: number): Promise<Review[]> {
  const rows = await db.review.findMany({ where: { productId, status: "APPROVED" }, orderBy: { createdAt: "desc" }, take: 50 });
  return rows.map((r) => ({
    id: r.id,
    productId: r.productId,
    author: r.authorName,
    rating: r.rating,
    title: r.title,
    body: r.body,
    createdAt: r.createdAt.toISOString(),
    verifiedPurchase: r.verifiedPurchase,
    skinTypeId: noNull(r.skinTypeId),
  }));
}

/** محصولات مشابه: هم‌دسته، سپس هم‌والد، سپس هم‌برند */
export async function getSimilarProducts(product: Product, limit = 8): Promise<ProductSummary[]> {
  const cats = await getCategoryIndex();
  const parentId = cats.byId.get(product.categoryId)?.parentId ?? product.categoryId;
  const rows = await db.product.findMany({
    where: {
      isActive: true,
      id: { not: product.id },
      OR: [{ categoryId: { in: cats.descendants(parentId) } }, { brandId: product.brandId }],
    },
    include: summaryInclude,
    orderBy: { soldCount: "desc" },
    take: 40,
  });
  const score = (p: SummaryRow) =>
    (p.categoryId === product.categoryId ? 3 : 0) + (cats.byId.get(p.categoryId)?.parentId === parentId ? 2 : 0) + (p.brandId === product.brandId ? 1 : 0);
  return rows.sort((a, b) => score(b) - score(a)).slice(0, limit).map((r) => toSummary(r));
}

// ── صفحه اصلی ───────────────────────────────────────────

export async function getBestsellers(limit = 10) {
  const rows = await db.product.findMany({ where: { isActive: true }, include: summaryInclude, orderBy: { soldCount: "desc" }, take: limit });
  return rows.map((r) => toSummary(r));
}

export async function getNewArrivals(limit = 10) {
  const rows = await db.product.findMany({ where: { isActive: true }, include: summaryInclude, orderBy: { createdAt: "desc" }, take: limit });
  return rows.map((r) => toSummary(r));
}

export async function getHomeBanners(): Promise<Banner[]> {
  const rows = await db.banner.findMany({ where: { isActive: true }, orderBy: { sortOrder: "asc" } });
  return rows.map((b) => ({
    id: b.id,
    eyebrow: b.eyebrow,
    title: b.title,
    subtitle: b.subtitle,
    cta: b.cta,
    href: b.href,
    theme: b.theme as Banner["theme"],
    art: b.art as unknown as Banner["art"],
    image: b.imageUrl ? { url: b.imageUrl, alt: b.imageAlt ?? b.title } : undefined,
  }));
}

// ── جستجوی پیشنهادی ─────────────────────────────────────

export async function suggest(q: string) {
  const query = normalizeFa(q);
  if (query.length < 2) return { products: [], brands: [], categories: [] };
  const [rows, brands, cats] = await Promise.all([
    db.product.findMany({ where: { isActive: true, ...searchWhere(query) }, include: summaryInclude, orderBy: { soldCount: "desc" }, take: 6 }),
    db.brand.findMany({ where: { isActive: true, OR: [{ name: { contains: query } }, { nameEn: { contains: query, mode: "insensitive" } }] }, take: 3 }),
    getCategoryIndex(),
  ]);
  return {
    products: rows.map((r) => toSummary(r)),
    brands: brands.map((b) => ({ name: b.name, slug: b.slug })),
    categories: cats.all
      .filter((c) => normalizeFa(c.name).includes(query))
      .slice(0, 3)
      .map((c) => ({ name: c.name, href: cats.href(c.id) })),
  };
}

// ── سبد خرید ───────────────────────────────

/** اطلاعات تنوع‌ها برای سبد خرید (قیمت همیشه از دیتابیس خوانده می‌شود) */
export async function getVariantsForCart(variantIds: string[]) {
  const rows = await db.productVariant.findMany({
    omit: PRIVATE_VARIANT_FIELDS,
    where: { id: { in: variantIds }, isActive: true, product: { isActive: true } },
    include: { product: { include: summaryInclude } },
  });
  // در سبد و سفارش، عکس همان رنگ انتخاب‌شده نمایش داده می‌شود
  return rows.map(({ product, ...v }) => ({ product: toSummary(product, v.id), variant: toVariant(v) }));
}

/** خلاصه محصولات با حفظ ترتیب شناسه‌ها */
export async function getSummariesByIds(ids: number[]): Promise<ProductSummary[]> {
  const rows = await db.product.findMany({ where: { id: { in: ids }, isActive: true }, include: summaryInclude });
  return ids.flatMap((id) => {
    const p = rows.find((r) => r.id === id);
    return p ? [toSummary(p)] : [];
  });
}

/** همه محصولات فعال (برای sitemap) */
export async function getAllProductRefs() {
  return db.product.findMany({ where: { isActive: true }, select: { id: true, slug: true, updatedAt: true } });
}

/** بعد از تغییر دسته‌ها در پنل، کش دسته‌ها پاک شود */
export function invalidateCategoryIndex() {
  categoryCache = undefined;
}
