package main

import (
	"context"
	"fmt"
	"math/rand"
	"strings"

	"github.com/k8scommerce/k8scommerce/internal/models"
	"github.com/k8scommerce/k8scommerce/internal/utils"

	"github.com/iancoleman/strcase"
	"github.com/icrowley/fake"

	"syreclabs.com/go/faker"
)

var nameMap map[string]bool = make(map[string]bool)

func createProducts() {
	p := &prod{}

	for i := 0; i < totalProducts; i++ {
		product := p.newProduct()
		p.addStoreProductRelation(product)
		p.addProductProperties(product)
		p.addProductCategories(product)
		p.createVariants(product)
	}

	repo.GetRawDB().Exec(`
		with cte as (
			select * from product
			order by random()
			limit 9
		)
		update product p 
		set featured = true
		from cte
		where p.id = cte.id`,
	)
}

func validateUniqueName(name string) string {
	if _, ok := nameMap[name]; ok {
		pn := fake.Product()
		return validateUniqueName(pn)
	}
	nameMap[name] = true
	return name
}

type prod struct {
	selectedOptionsMap    map[string]bool
	selectedPropertiesMap map[string]bool
	selectedCategoriesMap map[string]bool
}

func (a *prod) newProduct() *models.Product {
	productName := validateUniqueName(faker.Commerce().ProductName())
	description := toNullString(strings.Join(faker.Lorem().Paragraphs(2), " "))
	shortDescription := toNullString(description.String[0:64] + "...")

	product := &models.Product{
		StoreID:          storeID,
		Slug:             strcase.ToKebab(productName),
		Name:             productName,
		ShortDescription: shortDescription,
		Description:      description,
		MetaTitle:        toNullString(productName),
		MetaDescription:  shortDescription,
		MetaKeywords:     toNullString(strings.Join(strings.Split(shortDescription.String, " "), ",")),
		Promotionable:    true,
	}

	if err := product.Insert(context.Background(), db); err != nil {
		fmt.Printf("PRODUCT: %#v\n\n", product)
		panic(err)
	}

	return product
}

func (a *prod) pick(m map[string]int64) string {
	k := rand.Intn(len(m))
	for x := range m {
		if k == 0 {
			return x
		}
		k--
	}
	panic("unreachable")
}

func (a *prod) validateOptionIdx(idx string) string {
	if _, ok := a.selectedOptionsMap[idx]; ok {
		idx := a.pick(options)
		return a.validateOptionIdx(idx)
	}
	a.selectedOptionsMap[idx] = true
	return idx
}

func (a *prod) createVariants(product *models.Product) {

	a.selectedOptionsMap = make(map[string]bool)

	cartesianProductGroups := [][]string{}

	optionCount := utils.RandInt(1, len(options))
	for i := 0; i < int(optionCount); i++ {
		selectedIdx := a.validateOptionIdx(a.pick(options))

		items := optionItems[options[selectedIdx]]
		totalItems := len(items)
		start := utils.RandInt(0, totalItems)
		if start >= (totalItems / 2) {
			start = start - (totalItems / 2)
		}
		end := utils.RandInt(start+1, totalItems)
		if end-start >= 4 {
			end = start + 4
		}

		itemSelection := items[start:end]
		selected := []string{}
		for _, t := range itemSelection {
			selected = append(selected, optionItemNames[t])
		}
		cartesianProductGroups = append(cartesianProductGroups, selected)
	}

	sortOrder := 1
	a.addVariant(product.ID, true, product.Slug, sortOrder)
	sortOrder++

	for _, p := range utils.CartesianProduct(cartesianProductGroups) {
		skuAppend := strcase.ToKebab(strings.Join(p, "-"))
		variantSku := product.Slug + "-" + skuAppend
		variantSku = strings.ReplaceAll(variantSku, "/", "")
		variantSku = strings.ReplaceAll(variantSku, " ", "-")

		a.addVariant(product.ID, false, variantSku, sortOrder)
		sortOrder++
	}
}

func (a *prod) addVariant(productID int64, isDefault bool, sku string, sortOrder int) {

	record := &models.Variant{
		ProductID:      productID,
		IsDefault:      isDefault,
		Sku:            sku,
		CostAmount:     toNullInt64(int64(utils.RandInt(999, 10999))),
		CostCurrency:   toNullString("USD"),
		TrackInventory: true,
		SortOrder:      sortOrder,
	}
	if err := record.Insert(context.Background(), db); err != nil {
		panic(err)
	}

	a.addVariantPriceRelation(record)
}

func (a *prod) validatePropertyIdx(idx string) string {
	if _, ok := a.selectedPropertiesMap[idx]; ok {
		idx := a.pick(properties)
		return a.validatePropertyIdx(idx)
	}
	a.selectedPropertiesMap[idx] = true
	return idx
}

func (a *prod) addProductProperties(product *models.Product) {
	randPropertyCount := utils.RandInt(1, len(properties))

	a.selectedPropertiesMap = make(map[string]bool)
	for i := 0; i < int(randPropertyCount); i++ {
		selectedIdx := a.validatePropertyIdx(a.pick(properties))
		propertyID := properties[selectedIdx]
		if propertyID > 0 {
			a.addProductProperty(product.ID, propertyID)
		}
	}
}

func (a *prod) addProductProperty(productID, propertyID int64) {
	record := &models.ProductProperty{
		ProductID:  productID,
		PropertyID: propertyID,
	}
	if err := record.Insert(context.Background(), db); err != nil {
		panic(err)
	}
}

func (a *prod) addVariantPriceRelation(variant *models.Variant) {
	money1 := float64(variant.CostAmount.Int64) * utils.RandFloat(1.2, 3)
	money2 := float64(variant.CostAmount.Int64) * utils.RandFloat(3, 3.4)

	record := &models.Price{
		VariantID:       variant.ID,
		Amount:          int64(money1),
		Currency:        variant.CostCurrency,
		CompareAtAmount: toNullInt64(int64(money2)),
	}
	if err := record.Insert(context.Background(), db); err != nil {
		panic(err)
	}
}

func (a *prod) addStoreProductRelation(product *models.Product) {
	record := &models.StoreProduct{
		StoreID:   storeID,
		ProductID: product.ID,
	}
	if err := record.Insert(context.Background(), db); err != nil {
		panic(err)
	}
}

func (a *prod) validateCategoryIdx(idx string) string {
	if _, ok := a.selectedCategoriesMap[idx]; ok {
		idx := a.pick(categories)
		return a.validateCategoryIdx(idx)
	}
	a.selectedCategoriesMap[idx] = true
	return idx
}

func (a *prod) addProductCategories(product *models.Product) {

	a.selectedCategoriesMap = make(map[string]bool)
	for i := 0; i < len(categories); i++ {
		selectedIdx := a.validateCategoryIdx(a.pick(categories))
		categoryID := categories[selectedIdx]
		if categoryID > 0 {
			a.addProductCategoryRelation(product.ID, categoryID)
		}
	}
}

func (a *prod) addProductCategoryRelation(productID, categoryID int64) {
	record := &models.ProductCategory{
		ProductID:  productID,
		CategoryID: categoryID,
	}
	if err := record.Insert(context.Background(), db); err != nil {
		panic(err)
	}
}
package main

import (
	"context"
	"fmt"
	"math/rand"
	"strings"

	"github.com/k8scommerce/k8scommerce/internal/models"
	"github.com/k8scommerce/k8scommerce/internal/utils"

	"github.com/iancoleman/strcase"
	"github.com/icrowley/fake"

	// ifaker "github.com/jaswdr/faker"
	"syreclabs.com/go/faker"
)

var nameMap map[string]bool = make(map[string]bool)

func createProducts() {
	p := &prod{}

	for i := 0; i < totalProducts; i++ {
		product := p.newProduct()
		p.addStoreProductRelation(product)
		p.addProductProperties(product)
		p.addProductCategories(product)
		p.createVariants(product)
	}

	repo.GetRawDB().Exec(`
		with cte as (
			select * from product
			order by random()
			limit 9
		)
		update product p 
		set featured = true
		from cte
		where p.id = cte.id`,
	)
}

func validateUniqueName(name string) string {
	if _, ok := nameMap[name]; ok {
		pn := fake.Product()
		return validateUniqueName(pn)
	}
	nameMap[name] = true
	return name
}

type prod struct {
	selectedOptionsMap    map[string]bool
	selectedPropertiesMap map[string]bool
	selectedCategoriesMap map[string]bool
}

func (a *prod) newProduct() *models.Product {
	productName := validateUniqueName(faker.Commerce().ProductName())
	description := toNullString(strings.Join(faker.Lorem().Paragraphs(2), " "))
	shortDescription := toNullString(description.String[0:64] + "...")

	// Every product has to have a minimum of 1 variant
	// variants are created
	product := &models.Product{
		StoreID:          storeID,
		Slug:             strcase.ToKebab(productName),
		Name:             productName,
		ShortDescription: shortDescription,
		Description:      description,
		MetaTitle:        toNullString(productName),
		MetaDescription:  shortDescription,
		MetaKeywords:     toNullString(strings.Join(strings.Split(shortDescription.String, " "), ",")),
		Promotionable:    true,
	}

	if err := product.Insert(context.Background(), db); err != nil {
		fmt.Printf("PRODUCT: %#v\n\n", product)
		panic(err)
	}

	return product
}

func (a *prod) pick(m map[string]int64) string {
	k := rand.Intn(len(m))
	for x := range m {
		if k == 0 {
			return x
		}
		k--
	}
	panic("unreachable")
}

func (a *prod) validateOptionIdx(idx string) string {
	if _, ok := a.selectedOptionsMap[idx]; ok {
		idx := a.pick(options)
		return a.validateOptionIdx(idx)
	}
	a.selectedOptionsMap[idx] = true
	return idx
}

func (a *prod) createVariants(product *models.Product) {
	// each product will have at least 1 variant
	// the varients will be auto-generated by which
	// options are selected. We will select these at random

	// reset the selectedOptionsMap
	a.selectedOptionsMap = make(map[string]bool)

	// start with getting 1 or more random options
	// used to generate the skus
	cartesianProductGroups := [][]string{}
	// optionNames := []string{}
	optionCount := utils.RandInt(1, len(options))
	for i := 0; i < int(optionCount); i++ {
		selectedIdx := a.validateOptionIdx(a.pick(options))
		// optionNames = append(optionNames, selectedIdx)
		// get an assortment of the options
		items := optionItems[options[selectedIdx]]
		totalItems := len(items)
		start := utils.RandInt(0, totalItems)
		if start >= (totalItems / 2) {
			start = start - (totalItems / 2)
		}
		end := utils.RandInt(start+1, totalItems)
		if end-start >= 4 {
			end = start + 4
		}

		itemSelection := items[start:end]
		selected := []string{}
		for _, t := range itemSelection {
			selected = append(selected, optionItemNames[t])
		}
		cartesianProductGroups = append(cartesianProductGroups, selected)
	}

	// create the variants
	// start with the default
	sortOrder := 1
	a.addVariant(product.ID, true, product.Slug, sortOrder)
	sortOrder++

	// now create the others
	for _, p := range utils.CartesianProduct(cartesianProductGroups) {
		skuAppend := strcase.ToKebab(strings.Join(p, "-"))
		variantSku := product.Slug + "-" + skuAppend
		variantSku = strings.ReplaceAll(variantSku, "/", "")
		variantSku = strings.ReplaceAll(variantSku, " ", "-")

		a.addVariant(product.ID, false, variantSku, sortOrder)
		sortOrder++
	}
}

func (a *prod) addVariant(productID int64, isDefault bool, sku string, sortOrder int) {

	record := &models.Variant{
		ProductID:      productID,
		IsDefault:      isDefault,
		Sku:            sku,
		CostAmount:     toNullInt64(int64(utils.RandInt(999, 10999))),
		CostCurrency:   toNullString("USD"),
		TrackInventory: true,
		SortOrder:      sortOrder,
	}
	if err := record.Insert(context.Background(), db); err != nil {
		panic(err)
	}

	a.addVariantPriceRelation(record)
}

func (a *prod) validatePropertyIdx(idx string) string {
	if _, ok := a.selectedPropertiesMap[idx]; ok {
		idx := a.pick(properties)
		return a.validatePropertyIdx(idx)
	}
	a.selectedPropertiesMap[idx] = true
	return idx
}

func (a *prod) addProductProperties(product *models.Product) {
	randPropertyCount := utils.RandInt(1, len(properties))
	// reset the selectedPropertiesMap
	a.selectedPropertiesMap = make(map[string]bool)
	for i := 0; i < int(randPropertyCount); i++ {
		selectedIdx := a.validatePropertyIdx(a.pick(properties))
		propertyID := properties[selectedIdx]
		if propertyID > 0 {
			a.addProductProperty(product.ID, propertyID)
		}
	}
}

func (a *prod) addProductProperty(productID, propertyID int64) {
	record := &models.ProductProperty{
		ProductID:  productID,
		PropertyID: propertyID,
	}
	if err := record.Insert(context.Background(), db); err != nil {
		panic(err)
	}
}

func (a *prod) addVariantPriceRelation(variant *models.Variant) {
	money1 := float64(variant.CostAmount.Int64) * utils.RandFloat(1.2, 3)
	money2 := float64(variant.CostAmount.Int64) * utils.RandFloat(3, 3.4)

	record := &models.Price{
		VariantID:       variant.ID,
		Amount:          int64(money1),
		Currency:        variant.CostCurrency,
		CompareAtAmount: toNullInt64(int64(money2)),
	}
	if err := record.Insert(context.Background(), db); err != nil {
		panic(err)
	}
}

func (a *prod) addStoreProductRelation(product *models.Product) {
	record := &models.StoreProduct{
		StoreID:   storeID,
		ProductID: product.ID,
	}
	if err := record.Insert(context.Background(), db); err != nil {
		panic(err)
	}
}

func (a *prod) validateCategoryIdx(idx string) string {
	if _, ok := a.selectedCategoriesMap[idx]; ok {
		idx := a.pick(categories)
		return a.validateCategoryIdx(idx)
	}
	a.selectedCategoriesMap[idx] = true
	return idx
}

func (a *prod) addProductCategories(product *models.Product) {
	// randCategoryCount := utils.RandInt(1, len(categories))
	// reset the selectedCategoriesMap
	a.selectedCategoriesMap = make(map[string]bool)
	for i := 0; i < len(categories); i++ {
		selectedIdx := a.validateCategoryIdx(a.pick(categories))
		categoryID := categories[selectedIdx]
		if categoryID > 0 {
			a.addProductCategoryRelation(product.ID, categoryID)
		}
	}
}

func (a *prod) addProductCategoryRelation(productID, categoryID int64) {
	record := &models.ProductCategory{
		ProductID:  productID,
		CategoryID: categoryID,
	}
	if err := record.Insert(context.Background(), db); err != nil {
		panic(err)
	}
}
