package alignment_test

import (
	"testing"
	"unsafe"
)

// ===
// Comprehensive Entity Struct - Demonstrates All Common Go Types
// ===

// BadStruct represents a typical database entity with WORST CASE field alignment.
// Fields alternate between small and large types to maximize padding.
// This is an exaggerated example to clearly demonstrate the problem.
type BadStruct struct {
	IsActive   bool              // 1 byte  + 7 padding
	ID         uint64            // 8 bytes
	IsVerified bool              // 1 byte  + 7 padding
	Name       string            // 16 bytes
	IsAdmin    bool              // 1 byte  + 7 padding
	Score      float64           // 8 bytes
	IsPremium  bool              // 1 byte  + 3 padding
	ParentID   uint32            // 4 bytes
	TinyVal    int8              // 1 byte  + 1 padding
	SmallVal   int16             // 2 bytes + 4 padding
	Email      string            // 16 bytes
	IsDeleted  bool              // 1 byte  + 7 padding
	Count      int64             // 8 bytes
	IsArchived bool              // 1 byte  + 3 padding
	Rating     float32           // 4 bytes
	Status     int8              // 1 byte  + 7 padding
	Tags       []string          // 24 bytes
	Enabled    bool              // 1 byte  + 7 padding
	Metadata   map[string]string // 8 bytes
	Ready      bool              // 1 byte  + 7 padding
	CreatedAt  int64             // 8 bytes
	Done       bool              // 1 byte  + 7 padding
	UpdatedAt  int64             // 8 bytes
	Callback   func()            // 8 bytes
	Flag       bool              // 1 byte  + 7 padding
	Description string           // 16 bytes
}

// GoodStruct represents the same data with optimal field alignment.
// Fields ordered from largest alignment requirement to smallest:
// 1. Slices (24 bytes, 8-byte aligned)
// 2. Strings (16 bytes, 8-byte aligned)
// 3. 8-byte types (int64, uint64, float64, pointers, maps, funcs)
// 4. 4-byte types (int32, uint32, float32)
// 5. 2-byte types (int16, uint16)
// 6. 1-byte types (int8, uint8, bool, byte)
type GoodStruct struct {
	// Slices first (24 bytes, 8-byte aligned)
	Tags []string // 24 bytes

	// Strings (16 bytes, 8-byte aligned)
	Name        string // 16 bytes
	Email       string // 16 bytes
	Description string // 16 bytes

	// 8-byte types
	ID        uint64            // 8 bytes
	Count     int64             // 8 bytes
	Score     float64           // 8 bytes
	CreatedAt int64             // 8 bytes
	UpdatedAt int64             // 8 bytes
	Metadata  map[string]string // 8 bytes (pointer)
	Callback  func()            // 8 bytes (pointer)

	// 4-byte types
	ParentID uint32  // 4 bytes
	Rating   float32 // 4 bytes

	// 2-byte types
	SmallVal int16 // 2 bytes

	// 1-byte types (all packed together)
	TinyVal    int8 // 1 byte
	Status     int8 // 1 byte
	IsActive   bool // 1 byte
	IsVerified bool // 1 byte
	IsAdmin    bool // 1 byte
	IsPremium  bool // 1 byte
	IsDeleted  bool // 1 byte
	IsArchived bool // 1 byte
	Enabled    bool // 1 byte
	Ready      bool // 1 byte
	Done       bool // 1 byte
	Flag       bool // 1 byte + 2 padding (struct total must be 8-byte aligned)
}

// ===
// Size Verification
// ===

func TestEntitySizes(t *testing.T) {
	badSize := unsafe.Sizeof(BadStruct{})
	goodSize := unsafe.Sizeof(GoodStruct{})
	saved := badSize - goodSize
	pct := float64(saved) / float64(badSize) * 100

	t.Logf("BadStruct size:  %d bytes", badSize)
	t.Logf("GoodStruct size: %d bytes", goodSize)
	t.Logf("Memory saved:    %d bytes (%.1f%% reduction)", saved, pct)

	// Print field offsets for educational purposes
	t.Log("\n--- BadStruct field offsets ---")
	var bad BadStruct
	t.Logf("IsActive:    offset=%d, size=1", unsafe.Offsetof(bad.IsActive))
	t.Logf("ID:          offset=%d, size=8", unsafe.Offsetof(bad.ID))
	t.Logf("IsVerified:  offset=%d, size=1", unsafe.Offsetof(bad.IsVerified))
	t.Logf("Name:        offset=%d, size=16", unsafe.Offsetof(bad.Name))
	t.Logf("IsAdmin:     offset=%d, size=1", unsafe.Offsetof(bad.IsAdmin))
	t.Logf("Score:       offset=%d, size=8", unsafe.Offsetof(bad.Score))
	t.Logf("IsPremium:   offset=%d, size=1", unsafe.Offsetof(bad.IsPremium))
	t.Logf("ParentID:    offset=%d, size=4", unsafe.Offsetof(bad.ParentID))
	t.Logf("TinyVal:     offset=%d, size=1", unsafe.Offsetof(bad.TinyVal))
	t.Logf("SmallVal:    offset=%d, size=2", unsafe.Offsetof(bad.SmallVal))
	t.Logf("Email:       offset=%d, size=16", unsafe.Offsetof(bad.Email))
	t.Logf("...")
}

// ===
// Benchmarks
// ===

func BenchmarkBadStruct_Alloc(b *testing.B) {
	b.ReportAllocs()
	var s *BadStruct
	for i := 0; i < b.N; i++ {
		s = new(BadStruct)
	}
	_ = s
}

func BenchmarkGoodStruct_Alloc(b *testing.B) {
	b.ReportAllocs()
	var s *GoodStruct
	for i := 0; i < b.N; i++ {
		s = new(GoodStruct)
	}
	_ = s
}

func BenchmarkBadStruct_Slice1k(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		s := make([]BadStruct, 1000)
		_ = s
	}
}

func BenchmarkGoodStruct_Slice1k(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		s := make([]GoodStruct, 1000)
		_ = s
	}
}

func BenchmarkBadStruct_Slice10k(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		s := make([]BadStruct, 10000)
		_ = s
	}
}

func BenchmarkGoodStruct_Slice10k(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		s := make([]GoodStruct, 10000)
		_ = s
	}
}

func BenchmarkBadStruct_Iteration(b *testing.B) {
	data := make([]BadStruct, 10000)
	for i := range data {
		data[i].Count = int64(i)
	}
	b.ResetTimer()
	b.ReportAllocs()

	var sum int64
	for i := 0; i < b.N; i++ {
		sum = 0
		for j := range data {
			sum += data[j].Count
		}
	}
	_ = sum
}

func BenchmarkGoodStruct_Iteration(b *testing.B) {
	data := make([]GoodStruct, 10000)
	for i := range data {
		data[i].Count = int64(i)
	}
	b.ResetTimer()
	b.ReportAllocs()

	var sum int64
	for i := 0; i < b.N; i++ {
		sum = 0
		for j := range data {
			sum += data[j].Count
		}
	}
	_ = sum
}
