package generator import ( "crypto/md5" "fmt" "math/rand" "time" "go-faker/data" "go-faker/types" ) type Faker struct { rng *rand.Rand } func New(seed string) *Faker { s := hashSeed(seed) src := rand.NewSource(s) return &Faker{rng: rand.New(src)} } func hashSeed(seed string) int64 { h := md5.Sum([]byte(seed)) var result int64 for i := 0; i < 8; i++ { result = result<<8 | int64(h[i]) } return result } func (f *Faker) generateUUID() string { b := make([]byte, 16) f.rng.Read(b) return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) } func (f *Faker) generateSKU() string { return fmt.Sprintf("SKU-%08d", f.rng.Intn(99999999)) } func (f *Faker) generatePhone() string { prefix := data.PhonePrefixes[f.rng.Intn(len(data.PhonePrefixes))] number := fmt.Sprintf("%07d", f.rng.Intn(9999999)) return prefix + number } func (f *Faker) generateEmail(firstName, lastName string) string { domains := []string{"gmail.com", "yahoo.com", "outlook.com", "hotmail.com", "icloud.com", "telecom.net.et", "ethiotelecom.et"} domain := domains[f.rng.Intn(len(domains))] separator := []string{".", "_", ""} sep := separator[f.rng.Intn(len(separator))] variants := []string{ fmt.Sprintf("%s%s%s@%s", firstName, sep, lastName, domain), fmt.Sprintf("%s.%s@%s", firstName, lastName, domain), fmt.Sprintf("%s%d@%s", firstName, f.rng.Intn(999), domain), fmt.Sprintf("%s_%s%d@%s", firstName, lastName, f.rng.Intn(99), domain), } return variants[f.rng.Intn(len(variants))] } func (f *Faker) generateAge() int { return f.rng.Intn(72) + 18 } func (f *Faker) generateDateOfBirth() string { now := time.Now() maxAge := 72 minAge := 18 maxBirthDate := now.AddDate(-minAge, 0, 0) minBirthDate := now.AddDate(-maxAge, 0, 0) unixMin := minBirthDate.Unix() unixMax := maxBirthDate.Unix() unixBirth := unixMin + f.rng.Int63n(unixMax-unixMin) t := time.Unix(unixBirth, 0) return t.Format("2006-01-02") } func (f *Faker) generatePastDate(maxDaysAgo int) string { now := time.Now() daysAgo := f.rng.Intn(maxDaysAgo) past := now.AddDate(0, 0, -daysAgo) return past.Format("2006-01-02") } func (f *Faker) generateFutureDate(maxDaysAhead int) string { now := time.Now() daysAhead := f.rng.Intn(maxDaysAhead) future := now.AddDate(0, 0, daysAhead) return future.Format("2006-01-02") } func (f *Faker) generatePrice() float64 { return float64(f.rng.Intn(9999999))/100.0 + 0.50 } func (f *Faker) generateWeight() float64 { return float64(f.rng.Intn(1000))/10.0 + 0.1 } func (f *Faker) generateStock() int { return f.rng.Intn(1000) } func (f *Faker) generateCoordinates() (float64, float64) { lat := float64(f.rng.Intn(900000))/10000.0 + 3.0 lon := float64(f.rng.Intn(900000))/10000.0 + 33.0 return lat, lon } func (f *Faker) generateIPAddress() string { return fmt.Sprintf("10.%d.%d.%d", f.rng.Intn(256), f.rng.Intn(256), f.rng.Intn(256)) } func (f *Faker) generateSessionID() string { return fmt.Sprintf("sess-%s", f.generateUUID()) } func (f *Faker) generatePassword() string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*" password := make([]byte, 12) for i := range password { password[i] = charset[f.rng.Intn(len(charset))] } return string(password) } func (f *Faker) generatePostalCode() string { return fmt.Sprintf("%04d", f.rng.Intn(9999)) } func (f *Faker) generateHouseNumber() string { return fmt.Sprintf("House-%d", f.rng.Intn(999)+1) } func (f *Faker) generateAmount() float64 { return float64(f.rng.Intn(100000))/100.0 + 0.01 } func (f *Faker) pickString(arr []string) string { return arr[f.rng.Intn(len(arr))] } func (f *Faker) GeneratePerson(fields []string) map[string]any { firstName := f.pickString(data.EthiopianFirstNames) lastName := f.pickString(data.EthiopianLastNames) result := make(map[string]any) for _, field := range fields { switch field { case "first_name": result[field] = firstName case "last_name": result[field] = lastName case "full_name": result[field] = firstName + " " + lastName case "phone": result[field] = f.generatePhone() case "email": result[field] = f.generateEmail(firstName, lastName) case "gender": result[field] = f.pickString(data.Genders) case "age": result[field] = f.generateAge() case "language": result[field] = f.pickString(data.Languages) case "city": result[field] = f.pickString(data.EthiopianCities) case "region": result[field] = f.pickString(data.EthiopianRegions) case "nationality": result[field] = f.pickString(data.Nationalities) case "occupation": result[field] = f.pickString(data.Occupations) case "company": result[field] = f.pickString(data.Companies) case "blood_type": result[field] = f.pickString(data.BloodTypes) case "date_of_birth": result[field] = f.generateDateOfBirth() case "username": result[field] = fmt.Sprintf("%s%s%d", firstName, lastName, f.rng.Intn(99)) case "password": result[field] = f.generatePassword() } } return result } func (f *Faker) GenerateAddress(fields []string) map[string]any { result := make(map[string]any) city := f.pickString(data.EthiopianCities) for _, field := range fields { switch field { case "street": result[field] = f.pickString(data.EthiopianStreets) case "city": result[field] = city case "region": result[field] = f.pickString(data.EthiopianRegions) case "sub_city": if subCities, ok := data.EthiopianSubCities[city]; ok && len(subCities) > 0 { result[field] = f.pickString(subCities) } else { result[field] = city + " Sub City" } case "postal_code": result[field] = f.generatePostalCode() case "zone": result[field] = "Zone " + fmt.Sprintf("%d", f.rng.Intn(20)+1) case "woreda": result[field] = f.pickString(data.EthiopianWoredas) case "kebele": result[field] = fmt.Sprintf("Kebele %d", f.rng.Intn(15)+1) case "house_number": result[field] = f.generateHouseNumber() case "latitude", "longitude": lat, lon := f.generateCoordinates() result["latitude"] = lat result["longitude"] = lon } } return result } func (f *Faker) GenerateProduct(fields []string) map[string]any { result := make(map[string]any) for _, field := range fields { switch field { case "name": result[field] = f.pickString(data.ProductNames) case "category": result[field] = f.pickString(data.ProductCategories) case "price": result[field] = f.generatePrice() case "sku": result[field] = f.generateSKU() case "brand": result[field] = f.pickString(data.Brands) case "description": result[field] = "High quality " + f.pickString(data.ProductCategories) case "weight": result[field] = f.generateWeight() case "stock": result[field] = f.generateStock() case "expiry_date": result[field] = f.generateFutureDate(365*3) case "manufactured_date": result[field] = f.generatePastDate(365*2) } } return result } func (f *Faker) GenerateAnalytics(fields []string) map[string]any { result := make(map[string]any) for _, field := range fields { switch field { case "user_id": result[field] = "user-" + fmt.Sprintf("%d", f.rng.Intn(999999)) case "event": result[field] = f.pickString(data.EventTypes) case "timestamp": now := time.Now() secondsAgo := f.rng.Int63n(86400 * 30) t := time.Unix(now.Unix()-secondsAgo, 0) result[field] = t.Format(time.RFC3339) case "amount": result[field] = f.generateAmount() case "currency": result[field] = "ETB" case "session_id": result[field] = f.generateSessionID() case "page_url": result[field] = fmt.Sprintf("https://example.com/page-%d", f.rng.Intn(100)) case "ip_address": result[field] = f.generateIPAddress() case "device_type": result[field] = f.pickString(data.DeviceTypes) case "browser": result[field] = f.pickString(data.Browsers) case "os": result[field] = f.pickString(data.OperatingSystems) case "country": result[field] = f.pickString(data.Countries) case "referrer": result[field] = f.pickString(data.Referrers) } } return result } func (f *Faker) GenerateBatch(count int, dataType types.DataType, fields []string) []map[string]any { results := make([]map[string]any, count) for i := 0; i < count; i++ { switch dataType { case types.TypePerson: results[i] = f.GeneratePerson(fields) case types.TypeAddress: results[i] = f.GenerateAddress(fields) case types.TypeProduct: results[i] = f.GenerateProduct(fields) case types.TypeAnalytics: results[i] = f.GenerateAnalytics(fields) } } return results }