package seed

import (
	"errors"
	"fmt"
	"strings"
	"time"

	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"
	approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"

	"gorm.io/gorm"
)

func Run(db *gorm.DB) error {
	return db.Transaction(func(tx *gorm.DB) error {
		users, err := seedUsers(tx)
		if err != nil {
			return err
		}
		adminID := users["admin"]

		uoms, err := seedUoms(tx, adminID)
		if err != nil {
			return err
		}

		areas, err := seedAreas(tx, adminID)
		if err != nil {
			return err
		}

		locations, err := seedLocations(tx, adminID, areas)
		if err != nil {
			return err
		}

		productCategories, err := seedProductCategories(tx, adminID)
		if err != nil {
			return err
		}

		flocks, err := seedFlocks(tx, adminID)
		if err != nil {
			return err
		}

		fcrs, err := seedFcr(tx, adminID)
		if err != nil {
			return err
		}

		projectFlocks, err := seedProjectFlocks(tx, adminID, flocks, areas, fcrs, locations)
		if err != nil {
			return err
		}

		kandangs, err := seedKandangs(tx, adminID, locations, users, projectFlocks)
		if err != nil {
			return err
		}

		if err := seedWarehouses(tx, adminID, areas, locations, kandangs); err != nil {
			return err
		}

		suppliers, err := seedSuppliers(tx, adminID)
		if err != nil {
			return err
		}

		if err := seedCustomers(tx, adminID, users); err != nil {
			return err
		}

		if err := seedProducts(tx, adminID, uoms, productCategories, suppliers); err != nil {
			return err
		}

		if err := seedNonstocks(tx, adminID, uoms, suppliers); err != nil {
			return err
		}

		if err := seedBanks(tx, adminID); err != nil {
			return err
		}

		if err := seedProductWarehouse(tx, adminID); err != nil {
			return err
		}

		if err := seedTransferStock(tx, adminID); err != nil {
			return err
		}
		if err := seedChickin(tx, adminID); err != nil {
			return err
		}

		fmt.Println("✅ Master data seeding completed")
		return nil
	})
}

func seedUsers(tx *gorm.DB) (map[string]uint, error) {
	seeds := []struct {
		Key  string
		Data entity.User
	}{
		{
			Key:  "admin",
			Data: entity.User{Email: "admin@mbugroup.id", IdUser: 1, Name: "Super Admin"},
		},
	}

	result := make(map[string]uint, len(seeds))

	for _, seed := range seeds {
		var user entity.User
		err := tx.Where("email = ?", seed.Data.Email).First(&user).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			user = seed.Data
			if err := tx.Create(&user).Error; err != nil {
				return nil, err
			}
		} else if err != nil {
			return nil, err
		}
		result[seed.Key] = user.Id
	}

	return result, nil
}

func seedUoms(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
	names := []string{"Kilogram", "Gram", "Liter", "Unit", "Ekor"}
	result := make(map[string]uint, len(names))

	for _, name := range names {
		var uom entity.Uom
		err := tx.Where("name = ?", name).First(&uom).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			uom = entity.Uom{Name: name, CreatedBy: createdBy}
			if err := tx.Create(&uom).Error; err != nil {
				return nil, err
			}
		} else if err != nil {
			return nil, err
		}
		result[name] = uom.Id
	}

	return result, nil
}

func seedAreas(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
	names := []string{"Priangan", "Banten"}
	result := make(map[string]uint, len(names))

	for _, name := range names {
		var area entity.Area
		err := tx.Where("name = ?", name).First(&area).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			area = entity.Area{Name: name, CreatedBy: createdBy}
			if err := tx.Create(&area).Error; err != nil {
				return nil, err
			}
		} else if err != nil {
			return nil, err
		}
		result[name] = area.Id
	}

	return result, nil
}

func seedLocations(tx *gorm.DB, createdBy uint, areas map[string]uint) (map[string]uint, error) {
	seeds := []struct {
		Name    string
		Address string
		Area    string
	}{
		{"Singaparna", "Tasik", "Priangan"},
		{"Cikaum", "Cikaum", "Banten"},
	}

	result := make(map[string]uint, len(seeds))

	for _, seed := range seeds {
		areaID, ok := areas[seed.Area]
		if !ok {
			return nil, fmt.Errorf("area %s not seeded", seed.Area)
		}

		var loc entity.Location
		err := tx.Where("name = ?", seed.Name).First(&loc).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			loc = entity.Location{
				Name:      seed.Name,
				Address:   seed.Address,
				AreaId:    areaID,
				CreatedBy: createdBy,
			}
			if err := tx.Create(&loc).Error; err != nil {
				return nil, err
			}
		} else if err != nil {
			return nil, err
		}
		result[seed.Name] = loc.Id
	}

	return result, nil
}

func seedFlocks(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
	names := []string{"Flock Priangan", "Flock Banten"}
	result := make(map[string]uint, len(names))

	for _, name := range names {
		var flock entity.Flock
		err := tx.Where("name = ?", name).First(&flock).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			flock = entity.Flock{
				Name:      name,
				CreatedBy: createdBy,
			}
			if err := tx.Create(&flock).Error; err != nil {
				return nil, err
			}
		} else if err != nil {
			return nil, err
		} else {
			if err := tx.Model(&entity.Flock{}).Where("id = ?", flock.Id).Updates(map[string]any{
				"created_by": createdBy,
			}).Error; err != nil {
				return nil, err
			}
		}
		result[name] = flock.Id
	}

	return result, nil
}

func seedProjectFlocks(tx *gorm.DB, createdBy uint, flocks, areas, fcrs, locations map[string]uint) (map[string]uint, error) {
	seeds := []struct {
		Key      string
		Flock    string
		Area     string
		Category utils.ProjectFlockCategory
		Fcr      string
		Location string
		Period   int
	}{
		{
			Key:      "Singaparna Period 1",
			Flock:    "Flock Priangan",
			Area:     "Priangan",
			Category: utils.ProjectFlockCategoryGrowing,
			Fcr:      "FCR Layer",
			Location: "Singaparna",
			Period:   1,
		},
		{
			Key:      "Cikaum Period 1",
			Flock:    "Flock Banten",
			Area:     "Banten",
			Category: utils.ProjectFlockCategoryGrowing,
			Fcr:      "FCR Layer",
			Location: "Cikaum",
			Period:   1,
		},
	}

	result := make(map[string]uint, len(seeds))

	for _, seed := range seeds {
		flockID, ok := flocks[seed.Flock]
		if !ok {
			return nil, fmt.Errorf("floc %s not seeded", seed.Flock)
		}
		areaID, ok := areas[seed.Area]
		if !ok {
			return nil, fmt.Errorf("area %s not seeded", seed.Area)
		}
		fcrID, ok := fcrs[seed.Fcr]
		if !ok {
			return nil, fmt.Errorf("fcr %s not seeded", seed.Fcr)
		}
		locationID, ok := locations[seed.Location]
		if !ok {
			return nil, fmt.Errorf("location %s not seeded", seed.Location)
		}

		var projectFlock entity.ProjectFlock
		err := tx.Where("flock_id = ? AND area_id = ? AND category = ? AND fcr_id = ? AND location_id = ? AND period = ?",
			flockID, areaID, seed.Category, fcrID, locationID, seed.Period).First(&projectFlock).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			projectFlock = entity.ProjectFlock{
				FlockId:    flockID,
				AreaId:     areaID,
				Category:   string(seed.Category),
				FcrId:      fcrID,
				LocationId: locationID,
				Period:     seed.Period,
				CreatedBy:  createdBy,
			}
			if err := tx.Create(&projectFlock).Error; err != nil {
				return nil, err
			}
		} else if err != nil {
			return nil, err
		} else {
			if err := tx.Model(&entity.ProjectFlock{}).Where("id = ?", projectFlock.Id).Updates(map[string]any{
				"flock_id":    flockID,
				"area_id":     areaID,
				"category":    string(seed.Category),
				"fcr_id":      fcrID,
				"location_id": locationID,
				"period":      seed.Period,
			}).Error; err != nil {
				return nil, err
			}
		}

		if err := ensureProjectFlockApprovals(tx, projectFlock.Id, createdBy); err != nil {
			return nil, err
		}
		result[seed.Key] = projectFlock.Id
	}

	return result, nil
}

func ensureProjectFlockApprovals(tx *gorm.DB, projectFlockID uint, actorID uint) error {
	if projectFlockID == 0 || actorID == 0 {
		return nil
	}

	workflow := utils.ApprovalWorkflowProjectFlock.String()

	steps := []struct {
		step   approvalutils.ApprovalStep
		action entity.ApprovalAction
	}{
		{step: utils.ProjectFlockStepPengajuan, action: entity.ApprovalActionCreated},
		{step: utils.ProjectFlockStepAktif, action: entity.ApprovalActionApproved},
	}

	for _, cfg := range steps {
		var count int64
		if err := tx.Model(&entity.Approval{}).
			Where("approvable_type = ? AND approvable_id = ? AND step_number = ?", workflow, projectFlockID, uint16(cfg.step)).
			Count(&count).Error; err != nil {
			return err
		}
		if count > 0 {
			continue
		}

		stepName, ok := utils.ProjectFlockApprovalSteps[cfg.step]
		if !ok || strings.TrimSpace(stepName) == "" {
			stepName = fmt.Sprintf("Step %d", cfg.step)
		}

		var actionPtr *entity.ApprovalAction
		action := cfg.action
		actionPtr = &action

		record := entity.Approval{
			ApprovableType: workflow,
			ApprovableId:   projectFlockID,
			StepNumber:     uint16(cfg.step),
			StepName:       stepName,
			Action:         actionPtr,
			ActionBy:       uintPtr(actorID),
		}

		if err := tx.Create(&record).Error; err != nil {
			return err
		}
	}

	return nil
}

func seedKandangs(tx *gorm.DB, createdBy uint, locations map[string]uint, users map[string]uint, projectFlocks map[string]uint) (map[string]uint, error) {
	seeds := []struct {
		Name            string
		Status          utils.KandangStatus
		Location        string
		PicKey          string
		ProjectFlockKey *string
	}{
		{Name: "Singaparna 1", Status: utils.KandangStatusActive, Location: "Singaparna", PicKey: "admin", ProjectFlockKey: strPtr("Singaparna Period 1")},
		{Name: "Singaparna 2", Status: utils.KandangStatusNonActive, Location: "Singaparna", PicKey: "admin"},
		{Name: "Cikaum 1", Status: utils.KandangStatusActive, Location: "Cikaum", PicKey: "admin", ProjectFlockKey: strPtr("Cikaum Period 1")},
		{Name: "Cikaum 2", Status: utils.KandangStatusNonActive, Location: "Cikaum", PicKey: "admin"},
	}

	result := make(map[string]uint, len(seeds))

	for _, seed := range seeds {
		locID, ok := locations[seed.Location]
		if !ok {
			return nil, fmt.Errorf("location %s not seeded", seed.Location)
		}
		picID, ok := users[seed.PicKey]
		if !ok {
			return nil, fmt.Errorf("user %s not seeded", seed.PicKey)
		}

		var projectFlockID *uint
		if seed.ProjectFlockKey != nil {
			pfID, ok := projectFlocks[*seed.ProjectFlockKey]
			if !ok {
				return nil, fmt.Errorf("project flock %s not seeded", *seed.ProjectFlockKey)
			}
			projectFlockID = uintPtr(pfID)
		}

		var kandang entity.Kandang
		err := tx.Where("name = ?", seed.Name).First(&kandang).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			kandang = entity.Kandang{
				Name:           seed.Name,
				Status:         string(seed.Status),
				LocationId:     locID,
				PicId:          picID,
				ProjectFlockId: projectFlockID,
				CreatedBy:      createdBy,
			}
			if err := tx.Create(&kandang).Error; err != nil {
				return nil, err
			}
			if err := syncPivotRelation(tx, projectFlockID, kandang.Id); err != nil {
				return nil, err
			}
		} else if err != nil {
			return nil, err
		} else {
			updates := map[string]any{
				"location_id": locID,
				"pic_id":      picID,
				"status":      string(seed.Status),
			}
			if projectFlockID != nil {
				updates["project_flock_id"] = *projectFlockID
			} else {
				updates["project_flock_id"] = nil
			}
			if err := tx.Model(&entity.Kandang{}).Where("id = ?", kandang.Id).Updates(updates).Error; err != nil {
				return nil, err
			}
			if err := syncPivotRelation(tx, projectFlockID, kandang.Id); err != nil {
				return nil, err
			}
		}
		result[seed.Name] = kandang.Id
	}

	return result, nil
}

func syncPivotRelation(tx *gorm.DB, projectFlockID *uint, kandangID uint) error {
	if err := detachActivePivot(tx, kandangID); err != nil {
		return err
	}
	if projectFlockID == nil {
		return nil
	}
	return ensureActivePivot(tx, *projectFlockID, kandangID)
}

func detachActivePivot(tx *gorm.DB, kandangID uint) error {
	return tx.Where("kandang_id = ?", kandangID).
		Delete(&entity.ProjectFlockKandang{}).Error
}

func ensureActivePivot(tx *gorm.DB, projectFlockID, kandangID uint) error {
	var pivot entity.ProjectFlockKandang
	err := tx.Where("project_flock_id = ? AND kandang_id = ?", projectFlockID, kandangID).
		First(&pivot).Error
	if err == nil {
		return nil
	}
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return err
	}
	newRecord := entity.ProjectFlockKandang{
		ProjectFlockId: projectFlockID,
		KandangId:      kandangID,
	}
	return tx.Create(&newRecord).Error
}

func seedWarehouses(tx *gorm.DB, createdBy uint, areas map[string]uint, locations map[string]uint, kandangs map[string]uint) error {
	seeds := []struct {
		Name     string
		Type     string
		Area     string
		Location *string
		Kandang  *string
	}{
		{Name: "Gudang Priangan", Type: string(utils.WarehouseTypeArea), Area: "Priangan"},
		{Name: "Gudang Singaparna", Type: string(utils.WarehouseTypeLokasi), Area: "Priangan", Location: strPtr("Singaparna")},
		{Name: "Gudang Singaparna 1", Type: string(utils.WarehouseTypeKandang), Area: "Priangan", Location: strPtr("Singaparna"), Kandang: strPtr("Singaparna 1")},
		{Name: "Gudang Singaparna 2", Type: string(utils.WarehouseTypeKandang), Area: "Priangan", Location: strPtr("Singaparna"), Kandang: strPtr("Singaparna 2")},
		{Name: "Gudang Banten", Type: string(utils.WarehouseTypeArea), Area: "Banten"},
		{Name: "Gudang Cikaum", Type: string(utils.WarehouseTypeLokasi), Area: "Banten", Location: strPtr("Cikaum")},
		{Name: "Gudang Cikaum 1", Type: string(utils.WarehouseTypeKandang), Area: "Banten", Location: strPtr("Cikaum"), Kandang: strPtr("Cikaum 1")},
		{Name: "Gudang Cikaum 2", Type: string(utils.WarehouseTypeKandang), Area: "Banten", Location: strPtr("Cikaum"), Kandang: strPtr("Cikaum 2")},
	}

	for _, seed := range seeds {
		areaID, ok := areas[seed.Area]
		if !ok {
			return fmt.Errorf("area %s not seeded", seed.Area)
		}

		var warehouse entity.Warehouse
		err := tx.Where("name = ?", seed.Name).First(&warehouse).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			warehouse = entity.Warehouse{
				Name:      seed.Name,
				Type:      seed.Type,
				AreaId:    areaID,
				CreatedBy: createdBy,
			}
		} else if err != nil {
			return err
		}

		if seed.Location != nil {
			locID, ok := locations[*seed.Location]
			if !ok {
				return fmt.Errorf("location %s not seeded", *seed.Location)
			}
			warehouse.LocationId = uintPtr(locID)
		}
		if seed.Kandang != nil {
			kandangID, ok := kandangs[*seed.Kandang]
			if !ok {
				return fmt.Errorf("kandang %s not seeded", *seed.Kandang)
			}
			warehouse.KandangId = uintPtr(kandangID)
		}

		if warehouse.Id == 0 {
			if err := tx.Create(&warehouse).Error; err != nil {
				return err
			}
		} else {
			if err := tx.Model(&entity.Warehouse{}).Where("id = ?", warehouse.Id).Updates(map[string]any{
				"type":        warehouse.Type,
				"area_id":     warehouse.AreaId,
				"location_id": warehouse.LocationId,
				"kandang_id":  warehouse.KandangId,
			}).Error; err != nil {
				return err
			}
		}
	}

	return nil
}

func seedProductCategories(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
	seeds := []struct {
		Name string
		Code string
	}{
		{"Bahan Baku", "RAW"},
		{"Day Old Chick", "DOC"},
	}

	result := make(map[string]uint, len(seeds))

	for _, seed := range seeds {
		var category entity.ProductCategory
		err := tx.Where("name = ?", seed.Name).First(&category).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			category = entity.ProductCategory{Name: seed.Name, Code: seed.Code, CreatedBy: createdBy}
			if err := tx.Create(&category).Error; err != nil {
				return nil, err
			}
		} else if err != nil {
			return nil, err
		} else {
			if err := tx.Model(&entity.ProductCategory{}).Where("id = ?", category.Id).Updates(map[string]any{
				"code": seed.Code,
			}).Error; err != nil {
				return nil, err
			}
		}
		result[seed.Name] = category.Id
	}

	return result, nil
}

func seedSuppliers(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
	seeds := []struct {
		Name     string
		Alias    string
		Category string
		Email    string
		Phone    string
		Address  string
	}{
		{"PT CHAROEN POKPHAND INDONESIA Tbk", "CPI", string(utils.SupplierCategorySapronak), "cpi@gmail.com", "081200000001", "Jl. Pakan 1, Bekasi"},
		{"BOP Vendor", "BOP", string(utils.SupplierCategoryBOP), "bop@gmail.com", "081200000002", "Jl. Veteriner 3, Bogor"},
		{"Ekspedisi", "EKS", string(utils.SupplierCategoryBOP), "bop@gmail.com", "081200000002", "Jl. Veteriner 3, Bogor"},
	}

	result := make(map[string]uint, len(seeds))

	for idx, seed := range seeds {
		var supplier entity.Supplier
		err := tx.Where("name = ?", seed.Name).First(&supplier).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			supplier = entity.Supplier{
				Name:          seed.Name,
				Alias:         seed.Alias,
				Pic:           "John Doe",
				Type:          string(utils.CustomerSupplierTypeBisnis),
				Category:      seed.Category,
				Phone:         seed.Phone,
				Email:         seed.Email,
				Address:       seed.Address,
				DueDate:       30,
				CreatedBy:     createdBy,
				AccountNumber: strPtr(fmt.Sprintf("%03d", idx+1)),
			}
			if err := tx.Create(&supplier).Error; err != nil {
				return nil, err
			}
		} else if err != nil {
			return nil, err
		}
		result[seed.Name] = supplier.Id
	}

	return result, nil
}

func seedCustomers(tx *gorm.DB, createdBy uint, users map[string]uint) error {
	seeds := []struct {
		Name    string
		PicKey  string
		Address string
		Phone   string
		Email   string
	}{
		{"Abdul Azis", "admin", "Jl. Raya Utama 1, Bekasi", "082100000001", "abdul.azis@gmail.com"},
	}

	for idx, seed := range seeds {
		picID, ok := users[seed.PicKey]
		if !ok {
			return fmt.Errorf("user %s not seeded", seed.PicKey)
		}

		var customer entity.Customer
		err := tx.Where("name = ?", seed.Name).First(&customer).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			customer = entity.Customer{
				Name:          seed.Name,
				PicId:         picID,
				Type:          string(utils.CustomerSupplierTypeBisnis),
				Address:       seed.Address,
				Phone:         seed.Phone,
				Email:         seed.Email,
				AccountNumber: *strPtr(fmt.Sprintf("%03d", idx+1)),
				CreatedBy:     createdBy,
			}
			if err := tx.Create(&customer).Error; err != nil {
				return err
			}
		} else if err != nil {
			return err
		}
	}

	return nil
}

func seedFcr(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
	seeds := []struct {
		Name      string
		Standards []struct {
			Weight    float64
			FcrNumber float64
			Mortality float64
		}
	}{
		{
			Name: "FCR Layer",
			Standards: []struct {
				Weight    float64
				FcrNumber float64
				Mortality float64
			}{
				{Weight: 0.8, FcrNumber: 1.60, Mortality: 2.0},
				{Weight: 1.5, FcrNumber: 1.75, Mortality: 3.5},
			},
		},
	}

	result := make(map[string]uint, len(seeds))

	for _, seed := range seeds {
		var fcr entity.Fcr
		err := tx.Where("name = ?", seed.Name).First(&fcr).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			fcr = entity.Fcr{Name: seed.Name, CreatedBy: createdBy}
			if err := tx.Create(&fcr).Error; err != nil {
				return nil, err
			}
		} else if err != nil {
			return nil, err
		}
		result[seed.Name] = fcr.Id

		for _, std := range seed.Standards {
			var standard entity.FcrStandard
			err := tx.Where("fcr_id = ? AND weight = ?", fcr.Id, std.Weight).First(&standard).Error
			if errors.Is(err, gorm.ErrRecordNotFound) {
				standard = entity.FcrStandard{
					FcrID:     fcr.Id,
					Weight:    std.Weight,
					FcrNumber: std.FcrNumber,
					Mortality: std.Mortality,
				}
				if err := tx.Create(&standard).Error; err != nil {
					return nil, err
				}
			} else if err != nil {
				return nil, err
			} else {
				if err := tx.Model(&entity.FcrStandard{}).Where("id = ?", standard.Id).Updates(map[string]any{
					"fcr_number": std.FcrNumber,
					"mortality":  std.Mortality,
				}).Error; err != nil {
					return nil, err
				}
			}
		}
	}

	return result, nil
}

func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories map[string]uint, suppliers map[string]uint) error {
	seeds := []struct {
		Name      string
		Brand     string
		Sku       string
		Uom       string
		Category  string
		Price     float64
		Selling   *float64
		Tax       *float64
		Expiry    *int
		Suppliers []string
		Flags     []utils.FlagType
	}{
		{
			Name:      "DOC Broiler",
			Brand:     "MBU Broiler",
			Sku:       "BRO0001",
			Uom:       "Ekor",
			Category:  "Day Old Chick",
			Price:     7500,
			Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"},
			Flags:     []utils.FlagType{utils.FlagDOC},
		},
		{
			Name:      "281 SPECIAL STARTER",
			Brand:     "281 STARTER",
			Sku:       "281",
			Uom:       "Kilogram",
			Category:  "Bahan Baku",
			Price:     7850,
			Expiry:    intPtr(60),
			Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"},
			Flags:     []utils.FlagType{utils.FlagPakan, utils.FlagStarter},
		},
	}

	for _, seed := range seeds {
		uomID, ok := uoms[seed.Uom]
		if !ok {
			return fmt.Errorf("uom %s not seeded", seed.Uom)
		}
		categoryID, ok := categories[seed.Category]
		if !ok {
			return fmt.Errorf("product category %s not seeded", seed.Category)
		}

		var product entity.Product
		err := tx.Where("name = ?", seed.Name).First(&product).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			selling := seed.Selling
			tax := seed.Tax
			product = entity.Product{
				Name:              seed.Name,
				Brand:             seed.Brand,
				Sku:               &seed.Sku,
				UomId:             uomID,
				ProductCategoryId: categoryID,
				ProductPrice:      seed.Price,
				SellingPrice:      selling,
				Tax:               tax,
				ExpiryPeriod:      seed.Expiry,
				CreatedBy:         createdBy,
			}
			if err := tx.Create(&product).Error; err != nil {
				return err
			}
		} else if err != nil {
			return err
		} else {
			updates := map[string]any{
				"brand":               seed.Brand,
				"uom_id":              uomID,
				"product_category_id": categoryID,
				"product_price":       seed.Price,
				"selling_price":       seed.Selling,
				"tax":                 seed.Tax,
				"expiry_period":       seed.Expiry,
			}
			if seed.Sku != "" {
				updates["sku"] = seed.Sku
			}
			if err := tx.Model(&entity.Product{}).Where("id = ?", product.Id).Updates(updates).Error; err != nil {
				return err
			}
		}

		for _, supplierName := range seed.Suppliers {
			supplierID, ok := suppliers[supplierName]
			if !ok {
				return fmt.Errorf("supplier %s not seeded", supplierName)
			}
			var existing entity.ProductSupplier
			err := tx.Where("product_id = ? AND supplier_id = ?", product.Id, supplierID).First(&existing).Error
			if errors.Is(err, gorm.ErrRecordNotFound) {
				link := entity.ProductSupplier{ProductID: product.Id, SupplierID: supplierID}
				if err := tx.Create(&link).Error; err != nil {
					return err
				}
			} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
				return err
			}
		}

		if err := seedFlags(tx, product.Id, entity.FlagableTypeProduct, seed.Flags); err != nil {
			return err
		}
	}

	return nil
}

func seedNonstocks(tx *gorm.DB, createdBy uint, uoms map[string]uint, suppliers map[string]uint) error {
	seeds := []struct {
		Name      string
		Uom       string
		Suppliers []string
		Flags     []utils.FlagType
	}{
		{
			Name:      "Expedisi DOC",
			Uom:       "Ekor",
			Suppliers: []string{"Ekspedisi"},
			Flags:     []utils.FlagType{utils.FlagEkspedisi},
		},
		{
			Name:      "Solar",
			Uom:       "Liter",
			Suppliers: []string{"BOP Vendor"},
			Flags:     []utils.FlagType{},
		},
	}

	for _, seed := range seeds {
		uomID, ok := uoms[seed.Uom]
		if !ok {
			return fmt.Errorf("uom %s not seeded", seed.Uom)
		}

		var nonstock entity.Nonstock
		err := tx.Where("name = ?", seed.Name).First(&nonstock).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			nonstock = entity.Nonstock{
				Name:      seed.Name,
				UomId:     uomID,
				CreatedBy: createdBy,
			}
			if err := tx.Create(&nonstock).Error; err != nil {
				return err
			}
		} else if err != nil {
			return err
		} else {
			if err := tx.Model(&entity.Nonstock{}).Where("id = ?", nonstock.Id).Updates(map[string]any{
				"uom_id": uomID,
			}).Error; err != nil {
				return err
			}
		}

		for _, supplierName := range seed.Suppliers {
			supplierID, ok := suppliers[supplierName]
			if !ok {
				return fmt.Errorf("supplier %s not seeded", supplierName)
			}
			var existing entity.NonstockSupplier
			err := tx.Where("nonstock_id = ? AND supplier_id = ?", nonstock.Id, supplierID).First(&existing).Error
			if errors.Is(err, gorm.ErrRecordNotFound) {
				link := entity.NonstockSupplier{NonstockID: nonstock.Id, SupplierID: supplierID}
				if err := tx.Create(&link).Error; err != nil {
					return err
				}
			} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
				return err
			}
		}

		if err := seedFlags(tx, nonstock.Id, entity.FlagableTypeNonstock, seed.Flags); err != nil {
			return err
		}
	}

	return nil
}

// nanti saya isi

func seedFlags(tx *gorm.DB, flagableID uint, flagableType string, flags []utils.FlagType) error {
	if len(flags) == 0 {
		return nil
	}
	for _, flag := range flags {
		name := strings.ToUpper(string(flag))
		var existing entity.Flag
		err := tx.Where("name = ? AND flagable_id = ? AND flagable_type = ?", name, flagableID, flagableType).First(&existing).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			record := entity.Flag{
				Name:         name,
				FlagableID:   flagableID,
				FlagableType: flagableType,
			}
			if err := tx.Create(&record).Error; err != nil {
				return err
			}
		} else if err != nil {
			return err
		}
	}
	return nil
}

func seedBanks(tx *gorm.DB, createdBy uint) error {
	seeds := []struct {
		Name          string
		Alias         string
		Owner         *string
		AccountNumber string
	}{
		{
			Name:          "Bank Central Asia",
			Alias:         "BCA",
			AccountNumber: "1234567890",
			Owner:         ptr("PT MBU Group"),
		},
		{
			Name:          "Bank Rakyat Indonesia",
			Alias:         "BRI",
			AccountNumber: "9876543210",
			Owner:         ptr("PT MBU Group"),
		},
		{
			Name:          "Bank Mandiri",
			Alias:         "MAND",
			AccountNumber: "1122334455",
			Owner:         ptr("PT MBU Group"),
		},
	}

	for _, seed := range seeds {
		var bank entity.Bank
		err := tx.Where("name = ?", seed.Name).First(&bank).Error

		if errors.Is(err, gorm.ErrRecordNotFound) {
			bank = entity.Bank{
				Name:          seed.Name,
				Alias:         seed.Alias,
				Owner:         seed.Owner,
				AccountNumber: seed.AccountNumber,
				CreatedBy:     createdBy,
				CreatedAt:     time.Now(),
				UpdatedAt:     time.Now(),
			}
			if err := tx.Create(&bank).Error; err != nil {
				return err
			}
		} else if err != nil {
			return err
		} else {
			// update data jika sudah ada
			if err := tx.Model(&entity.Bank{}).Where("id = ?", bank.Id).Updates(map[string]any{
				"alias":          seed.Alias,
				"owner":          seed.Owner,
				"account_number": seed.AccountNumber,
				"updated_at":     time.Now(),
			}).Error; err != nil {
				return err
			}
		}
	}

	return nil
}

func seedProductWarehouse(tx *gorm.DB, createdBy uint) error {

	seeds := []struct {
		ProductID   uint
		WarehouseID uint
		Quantity    float64
	}{
		{ProductID: 1, WarehouseID: 1, Quantity: 100},
		{ProductID: 2, WarehouseID: 2, Quantity: 200},
		{ProductID: 2, WarehouseID: 1, Quantity: 300},
		{ProductID: 1, WarehouseID: 3, Quantity: 5000},
	}

	for _, seed := range seeds {
		var productWarehouse entity.ProductWarehouse
		err := tx.Where("product_id = ? AND warehouse_id = ?", seed.ProductID, seed.WarehouseID).First(&productWarehouse).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			productWarehouse = entity.ProductWarehouse{
				ProductId:   seed.ProductID,
				WarehouseId: seed.WarehouseID,
				Quantity:    seed.Quantity,
				CreatedBy:   createdBy,
			}
			if err := tx.Create(&productWarehouse).Error; err != nil {
				return err
			}
		} else if err != nil {
			return err
		}
	}

	return nil
}

func seedTransferStock(tx *gorm.DB, createdBy uint) error {

	transfer := entity.StockTransfer{
		FromWarehouseId: 1,
		ToWarehouseId:   2,
		Reason:          "Seed transfer stock",
		TransferDate:    time.Now(),
		MovementNumber:  "SEED-TRF-00001",
		CreatedBy:       1,
	}
	if err := tx.Create(&transfer).Error; err != nil {
		return err
	}

	details := []entity.StockTransferDetail{
		{
			StockTransferId: transfer.Id,
			ProductId:       1,
			Quantity:        10,
		},
		{
			StockTransferId: transfer.Id,
			ProductId:       2,
			Quantity:        5,
		},
	}
	for i := range details {
		if err := tx.Create(&details[i]).Error; err != nil {
			return err
		}
	}

	deliveries := []entity.StockTransferDelivery{
		{
			StockTransferId:   transfer.Id,
			SupplierId:        1,
			VehiclePlate:      "B 1234 XYZ",
			DriverName:        "Driver Seed",
			DocumentPath:      "seed.pdf",
			ShippingCostItem:  1000,
			ShippingCostTotal: 2000,
		},
	}
	for i := range deliveries {
		if err := tx.Create(&deliveries[i]).Error; err != nil {
			return err
		}
	}

	detailMap := make(map[uint64]uint64)
	for _, d := range details {
		detailMap[d.ProductId] = d.Id
	}

	deliveryItems := []entity.StockTransferDeliveryItem{
		{
			StockTransferDeliveryId: deliveries[0].Id,
			StockTransferDetailId:   detailMap[1],
			Quantity:                50,
		},
		{
			StockTransferDeliveryId: deliveries[0].Id,
			StockTransferDetailId:   detailMap[2],
			Quantity:                30,
		},
	}
	for i := range deliveryItems {
		if err := tx.Create(&deliveryItems[i]).Error; err != nil {
			return err
		}
	}

	return nil
}

func seedChickin(tx *gorm.DB, createdBy uint) error {
	seeds := []struct {
		ProjectFlockKandangId uint
		ChickInDate           string
		Quantity              float64
		Note                  string
	}{
		{ProjectFlockKandangId: 1, ChickInDate: "2025-10-20", Quantity: 100, Note: "Seeder chickin 1"},
		{ProjectFlockKandangId: 2, ChickInDate: "2025-10-21", Quantity: 200, Note: "Seeder chickin 2"},
	}

	for _, seed := range seeds {
		chickinDate, err := time.Parse("2006-01-02", seed.ChickInDate)
		if err != nil {
			return err
		}

		// Insert ProjectChickin jika belum ada
		var chickin entity.ProjectChickin
		err = tx.Where("project_flock_kandang_id = ? AND chick_in_date = ?", seed.ProjectFlockKandangId, chickinDate).
			First(&chickin).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			chickin = entity.ProjectChickin{
				ProjectFlockKandangId: seed.ProjectFlockKandangId,
				ChickInDate:           chickinDate,
				Quantity:              seed.Quantity,
				Note:                  seed.Note,
				CreatedBy:             createdBy,
			}
			if err := tx.Create(&chickin).Error; err != nil {
				return err
			}
		} else if err != nil {
			return err
		}

		var population entity.ProjectFlockPopulation
		err = tx.Where("project_flock_kandang_id = ?", seed.ProjectFlockKandangId).First(&population).Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			population = entity.ProjectFlockPopulation{
				ProjectFlockKandangId: seed.ProjectFlockKandangId,
				InitialQuantity:       seed.Quantity,
				CurrentQuantity:       seed.Quantity,
				ReservedQuantity:      0,
				CreatedBy:             createdBy,
			}
			if err := tx.Create(&population).Error; err != nil {
				return err
			}
		} else if err != nil {
			return err
		} else {
			// Update population quantities
			if err := tx.Model(&entity.ProjectFlockPopulation{}).
				Where("id = ?", population.Id).
				Updates(map[string]any{
					"initial_quantity":  population.InitialQuantity + seed.Quantity,
					"current_quantity":  population.CurrentQuantity + seed.Quantity,
					"reserved_quantity": 0,
				}).Error; err != nil {
				return err
			}
		}

		var pfk entity.ProjectFlockKandang
		if err := tx.Where("id = ?", seed.ProjectFlockKandangId).First(&pfk).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				// no pivot found; skip creating details
				continue
			}
			return err
		}

		var warehouse entity.Warehouse
		if err := tx.Where("kandang_id = ?", pfk.KandangId).First(&warehouse).Error; err != nil {
			// if warehouse not found, cannot create details
			if errors.Is(err, gorm.ErrRecordNotFound) {
				continue
			}
			return err
		}

		var productWarehouses []entity.ProductWarehouse
		err = tx.Table("product_warehouses").
			Select("product_warehouses.*").
			Joins("JOIN products ON products.id = product_warehouses.product_id").
			Joins("JOIN product_categories ON product_categories.id = products.product_category_id").
			Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", "DOC", warehouse.Id).
			Order("product_warehouses.created_at DESC").
			Find(&productWarehouses).Error
		if err != nil {
			return err
		}

		// If no product warehouses found, keep existing chickin.Quantity and skip details
		if len(productWarehouses) == 0 {
			continue
		}

		// sum all pw quantities and set chickin.Quantity to that total (mimic CreateOne)
		totalQty := 0.0
		for _, pw := range productWarehouses {
			totalQty += pw.Quantity
		}

		if chickin.Quantity != totalQty {
			if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Update("quantity", totalQty).Error; err != nil {
				return err
			}
			chickin.Quantity = totalQty
		}

		for _, pw := range productWarehouses {
			// ensure detail exists or create it with full pw.Quantity
			var detail entity.ProjectChickinDetail
			err = tx.Where("project_chickin_id = ? AND product_warehouse_id = ?", chickin.Id, pw.Id).First(&detail).Error
			if errors.Is(err, gorm.ErrRecordNotFound) {
				detail = entity.ProjectChickinDetail{
					ProjectChickinId:   chickin.Id,
					ProductWarehouseId: pw.Id,
					Quantity:           pw.Quantity,
					CreatedBy:          createdBy,
				}
				if err := tx.Create(&detail).Error; err != nil {
					return err
				}
			} else if err != nil {
				return err
			} else {
				if detail.Quantity != pw.Quantity {
					if err := tx.Model(&entity.ProjectChickinDetail{}).Where("id = ?", detail.Id).Update("quantity", pw.Quantity).Error; err != nil {
						return err
					}
				}
			}

			// zero out pw quantity
			if err := tx.Model(&entity.ProductWarehouse{}).Where("id = ?", pw.Id).Update("quantity", 0).Error; err != nil {
				return err
			}
		}
	}

	return nil
}

func ptr[T any](v T) *T {
	return &v
}

func strPtr(s string) *string {
	return &s
}

func intPtr(v int) *int {
	return &v
}

func uintPtr(v uint) *uint {
	return &v
}
