// Seeds the catalogue from brand/brand.json, which is also what drives the
// Instagram poster renderer. One file, one truth. Safe to run repeatedly.
import { PrismaClient } from '@prisma/client';
import * as fs from 'fs';
import * as path from 'path';

const prisma = new PrismaClient();

type BrandProduct = {
  slug: string; name: string; subtitle?: string; size?: string;
  price: number; compareAt?: number; stock?: number; sortOrder?: number;
  isBundle?: boolean; bundleOf?: { slug: string; quantity: number }[];
  ingredientsPartial?: boolean; ingredientsCount?: number;
  summary?: string; howToUse?: string; bullets?: string[];
  ingredients?: string[]; benefits?: string[]; images?: string[];
};

function loadBrand() {
  for (const p of ['/brand/brand.json', path.join(__dirname, '../../brand/brand.json')]) {
    if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf8'));
  }
  throw new Error('brand.json not found');
}

async function main() {
  const brand = loadBrand();

  // Order numbers come from Postgres so two checkouts cannot collide.
  await prisma.$executeRawUnsafe(`CREATE SEQUENCE IF NOT EXISTS order_number_seq START 1000`);

  const products: BrandProduct[] = brand.products ?? [];
  if (!products.length) throw new Error('brand.json has no products array');

  for (const p of products) {
    const data = {
      name: p.name,
      subtitle: p.subtitle ?? null,
      sizeLabel: p.size ?? null,
      description: p.summary ?? null,
      howToUse: p.howToUse ?? null,
      pricePaisa: Math.round(p.price * 100),
      comparePaisa: p.compareAt ? Math.round(p.compareAt * 100) : null,
      // A partial list is marked as one. Claiming a count the list does not
      // reach, without saying so, is the kind of thing a customer checks.
      ingredients: p.ingredientsPartial && p.ingredientsCount
        ? [...(p.ingredients ?? []), `and more, ${p.ingredientsCount} in total`]
        : (p.ingredients ?? []),
      benefits: p.benefits ?? [],
      badges: brand.badges ?? [],
      sortOrder: p.sortOrder ?? 0,
      isBundle: p.isBundle ?? false,
    };

    // Price, sale price and stock are live trading data. Once the shop is
    // running they belong to whoever is using the admin, and a reseed must
    // never quietly undo their work. Only descriptive fields are synced.
    const { pricePaisa, comparePaisa, ...describable } = data;

    const saved = await prisma.product.upsert({
      where: { slug: p.slug },
      update: describable,
      create: { ...data, slug: p.slug, stock: p.stock ?? 0 },
    });

    const images = p.images ?? [];
    if (images.length) {
      await prisma.productImage.deleteMany({ where: { productId: saved.id } });
      await prisma.productImage.createMany({
        data: images.map((url, i) => ({
          productId: saved.id, url, alt: p.name, isPrimary: i === 0, sortOrder: i,
        })),
      });
    }
    console.log(`seeded ${p.slug}  ${p.price} PKR  ${images.length} image(s)`);
  }

  // Bundle contents are linked in a second pass, once every product exists.
  for (const p of products.filter((x) => x.bundleOf?.length)) {
    const bundle = await prisma.product.findUniqueOrThrow({ where: { slug: p.slug } });
    await prisma.bundleItem.deleteMany({ where: { bundleId: bundle.id } });
    for (const part of p.bundleOf!) {
      const component = await prisma.product.findUniqueOrThrow({ where: { slug: part.slug } });
      await prisma.bundleItem.create({
        data: { bundleId: bundle.id, componentId: component.id, quantity: part.quantity },
      });
    }
    console.log(`  ${p.slug} contains ${p.bundleOf!.map((x) => x.slug).join(' + ')}`);
  }
}

main()
  .catch((e) => { console.error(e); process.exit(1); })
  .finally(() => prisma.$disconnect());
