package uuid

import (
	"regexp"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestNew tests UUID generation
func TestNew(t *testing.T) {
	tests := []struct {
		name string
		runs int
	}{
		{
			name: "generate single UUID",
			runs: 1,
		},
		{
			name: "generate multiple UUIDs",
			runs: 100,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			seen := make(map[string]bool)

			for i := 0; i < tt.runs; i++ {
				uuid := New()

				// Verify UUID is 16 bytes
				assert.Equal(t, 16, len(uuid))

				// Verify version is 4
				version := (uuid[6] >> 4) & 0x0f
				assert.Equal(t, uint8(4), version, "UUID version should be 4")

				// Verify variant is RFC 4122
				variant := (uuid[8] >> 6) & 0x03
				assert.Equal(t, uint8(2), variant, "UUID variant should be RFC 4122 (10 in binary)")

				// Check uniqueness
				str := uuid.String()
				assert.False(t, seen[str], "UUID should be unique")
				seen[str] = true
			}
		})
	}
}

// TestString tests UUID string formatting
func TestString(t *testing.T) {
	tests := []struct {
		name     string
		expected string
		uuid     UUID
	}{
		{
			name:     "zero UUID",
			uuid:     UUID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
			expected: "00000000-0000-0000-0000-000000000000",
		},
		{
			name:     "all ones UUID",
			uuid:     UUID{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
			expected: "ffffffff-ffff-ffff-ffff-ffffffffffff",
		},
		{
			name:     "mixed values UUID",
			uuid:     UUID{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88},
			expected: "12345678-9abc-def0-1122-334455667788",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			str := tt.uuid.String()
			assert.Equal(t, tt.expected, str)

			// Verify format matches UUID regex
			matched, err := regexp.MatchString(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, str)
			require.NoError(t, err)
			assert.True(t, matched, "UUID string should match standard format")

			// Verify dashes are in correct positions
			assert.Equal(t, "-", string(str[8]))
			assert.Equal(t, "-", string(str[13]))
			assert.Equal(t, "-", string(str[18]))
			assert.Equal(t, "-", string(str[23]))

			// Verify length
			assert.Equal(t, 36, len(str))
		})
	}
}

// TestUUIDFormat tests that generated UUIDs match the standard format
func TestUUIDFormat(t *testing.T) {
	const iterations = 1000

	// Compile regex once for performance
	hexPattern := regexp.MustCompile(`^[0-9a-f]+$`)

	for i := 0; i < iterations; i++ {
		uuid := New()
		str := uuid.String()

		// Test standard UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
		parts := strings.Split(str, "-")
		require.Len(t, parts, 5, "UUID should have 5 parts separated by dashes")
		assert.Equal(t, 8, len(parts[0]), "First part should be 8 characters")
		assert.Equal(t, 4, len(parts[1]), "Second part should be 4 characters")
		assert.Equal(t, 4, len(parts[2]), "Third part should be 4 characters")
		assert.Equal(t, 4, len(parts[3]), "Fourth part should be 4 characters")
		assert.Equal(t, 12, len(parts[4]), "Fifth part should be 12 characters")

		// Verify all characters are hexadecimal
		for _, part := range parts {
			assert.True(t, hexPattern.MatchString(part), "UUID parts should only contain hex characters")
		}

		// Verify version bits (4th character of third part should start with 4)
		versionChar := parts[2][0]
		assert.Equal(t, byte('4'), versionChar, "UUID version should be 4")

		// Verify variant bits (first character of fourth part should be 8, 9, a, or b)
		variantChar := parts[3][0]
		assert.Contains(t, []byte{'8', '9', 'a', 'b'}, variantChar, "UUID variant should be RFC 4122")
	}
}

// TestConcurrentGeneration tests that UUID generation is safe for concurrent use
func TestConcurrentGeneration(t *testing.T) {
	const numGoroutines = 100
	const uuidsPerGoroutine = 100

	results := make(chan UUID, numGoroutines*uuidsPerGoroutine)

	// Generate UUIDs concurrently
	for i := 0; i < numGoroutines; i++ {
		go func() {
			for j := 0; j < uuidsPerGoroutine; j++ {
				results <- New()
			}
		}()
	}

	// Collect all UUIDs
	seen := make(map[string]bool)
	for i := 0; i < numGoroutines*uuidsPerGoroutine; i++ {
		uuid := <-results
		str := uuid.String()

		// Verify uniqueness
		assert.False(t, seen[str], "UUID should be unique even in concurrent generation")
		seen[str] = true

		// Verify version and variant
		version := (uuid[6] >> 4) & 0x0f
		assert.Equal(t, uint8(4), version)

		variant := (uuid[8] >> 6) & 0x03
		assert.Equal(t, uint8(2), variant)
	}

	// Verify we got all expected UUIDs
	assert.Equal(t, numGoroutines*uuidsPerGoroutine, len(seen))
}

// TestUUIDEquality tests UUID equality
func TestUUIDEquality(t *testing.T) {
	uuid1 := New()
	uuid2 := New()

	// Different UUIDs should not be equal
	assert.NotEqual(t, uuid1, uuid2)
	assert.NotEqual(t, uuid1.String(), uuid2.String())

	// Same UUID should be equal
	uuid3 := uuid1
	assert.Equal(t, uuid1, uuid3)
	assert.Equal(t, uuid1.String(), uuid3.String())
}

// TestUUIDArrayAccess tests that UUID can be accessed as a byte array
func TestUUIDArrayAccess(t *testing.T) {
	uuid := New()

	// Verify we can access all bytes
	for i := 0; i < 16; i++ {
		_ = uuid[i]
	}

	// Verify length
	assert.Equal(t, 16, len(uuid))
}

// BenchmarkNew benchmarks UUID generation
func BenchmarkNew(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = New()
	}
}

// BenchmarkString benchmarks UUID string conversion
func BenchmarkString(b *testing.B) {
	uuid := New()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		_ = uuid.String()
	}
}
