package jdenticon

import (
	"strings"
	"testing"
)

func TestComputeHash(t *testing.T) {
	tests := []struct {
		name     string
		input    interface{}
		expected string // Known SHA-1 hash values
	}{
		{
			name:     "empty string",
			input:    "",
			expected: "da39a3ee5e6b4b0d3255bfef95601890afd80709",
		},
		{
			name:     "simple string",
			input:    "hello",
			expected: "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d",
		},
		{
			name:     "email address",
			input:    "user@example.com",
			expected: "63a710569261a24b3766275b7000ce8d7b32e2f7",
		},
		{
			name:     "nil input",
			input:    nil,
			expected: "da39a3ee5e6b4b0d3255bfef95601890afd80709", // Same as empty string
		},
		{
			name:     "integer input",
			input:    123,
			expected: "40bd001563085fc35165329ea1ff5c5ecbdbbeef", // SHA-1 of "123"
		},
		{
			name:     "byte slice input",
			input:    []byte("test"),
			expected: "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := ComputeHash(tt.input)
			if result != tt.expected {
				t.Errorf("ComputeHash(%v) = %s, want %s", tt.input, result, tt.expected)
			}
		})
	}
}

func TestHashValue(t *testing.T) {
	// Test that HashValue produces the same result as ComputeHash for strings
	testStrings := []string{"", "hello", "user@example.com", "test123"}
	
	for _, str := range testStrings {
		hashValue := HashValue(str)
		computeHash := ComputeHash(str)
		
		if hashValue != computeHash {
			t.Errorf("HashValue(%s) = %s, but ComputeHash(%s) = %s", str, hashValue, str, computeHash)
		}
	}
}

func TestIsValidHash(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected bool
	}{
		{
			name:     "valid long hash",
			input:    "da39a3ee5e6b4b0d3255bfef95601890afd80709",
			expected: true,
		},
		{
			name:     "valid minimum length",
			input:    "da39a3ee5e6",
			expected: true,
		},
		{
			name:     "too short",
			input:    "da39a3ee5e",
			expected: false,
		},
		{
			name:     "invalid character",
			input:    "da39a3ee5e6g4b0d3255bfef95601890afd80709",
			expected: false,
		},
		{
			name:     "uppercase valid",
			input:    "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709",
			expected: true,
		},
		{
			name:     "empty string",
			input:    "",
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isValidHash(tt.input)
			if result != tt.expected {
				t.Errorf("isValidHash(%s) = %v, want %v", tt.input, result, tt.expected)
			}
		})
	}
}

func TestParseHex(t *testing.T) {
	hash := "da39a3ee5e6b4b0d3255bfef95601890afd80709"
	
	tests := []struct {
		name          string
		startPosition int
		octets        int
		expected      int
	}{
		{
			name:          "first character",
			startPosition: 0,
			octets:        1,
			expected:      0xd, // 'd' in hex
		},
		{
			name:          "two characters",
			startPosition: 0,
			octets:        2,
			expected:      0xda,
		},
		{
			name:          "middle position",
			startPosition: 10,
			octets:        1,
			expected:      0x6, // '6' at position 10
		},
		{
			name:          "negative index",
			startPosition: -1,
			octets:        1,
			expected:      0x9, // last character '9'
		},
		{
			name:          "negative index multiple chars",
			startPosition: -2,
			octets:        2,
			expected:      0x09, // last two characters "09"
		},
		{
			name:          "out of bounds",
			startPosition: 100,
			octets:        1,
			expected:      0,
		},
		{
			name:          "zero octets reads to end",
			startPosition: -7,
			octets:        0,
			expected:      0xfd80709, // Last 7 characters "fd80709"
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := parseHex(hash, tt.startPosition, tt.octets)
			if err != nil {
				// For out of bounds case, we expect an error but return 0
				if tt.expected == 0 && tt.name == "out of bounds" {
					// This is expected
					return
				}
				t.Errorf("parseHex(%s, %d, %d) unexpected error: %v", hash, tt.startPosition, tt.octets, err)
				return
			}
			if result != tt.expected {
				t.Errorf("parseHex(%s, %d, %d) = %d, want %d", hash, tt.startPosition, tt.octets, result, tt.expected)
			}
		})
	}
}

func TestParseHexErrors(t *testing.T) {
	tests := []struct {
		name          string
		hash          string
		startPosition int
		octets        int
		expectError   bool
		errorContains string
	}{
		{
			name:          "invalid hex character",
			hash:          "da39g3ee5e6b4b0d3255bfef95601890afd80709",
			startPosition: 4,
			octets:        1,
			expectError:   true,
			errorContains: "failed to parse hex",
		},
		{
			name:          "out of bounds positive",
			hash:          "da39a3ee",
			startPosition: 10,
			octets:        1,
			expectError:   true,
			errorContains: "out of bounds",
		},
		{
			name:          "out of bounds negative",
			hash:          "da39a3ee",
			startPosition: -20,
			octets:        1,
			expectError:   true,
			errorContains: "out of bounds",
		},
		{
			name:          "valid hex",
			hash:          "da39a3ee5e6b4b0d3255bfef95601890afd80709",
			startPosition: 0,
			octets:        2,
			expectError:   false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := parseHex(tt.hash, tt.startPosition, tt.octets)
			
			if tt.expectError {
				if err == nil {
					t.Errorf("parseHex(%s, %d, %d) expected error, got nil", tt.hash, tt.startPosition, tt.octets)
					return
				}
				if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) {
					t.Errorf("parseHex(%s, %d, %d) error = %v, want error containing %s", tt.hash, tt.startPosition, tt.octets, err, tt.errorContains)
				}
			} else {
				if err != nil {
					t.Errorf("parseHex(%s, %d, %d) unexpected error: %v", tt.hash, tt.startPosition, tt.octets, err)
				}
				// For valid case, just ensure no error and result >= 0
				if result < 0 {
					t.Errorf("parseHex(%s, %d, %d) = %d, want >= 0", tt.hash, tt.startPosition, tt.octets, result)
				}
			}
		})
	}
}

func TestParseHash(t *testing.T) {
	tests := []struct {
		name        string
		input       string
		expectError bool
		expected    []byte
	}{
		{
			name:        "valid hex string",
			input:       "da39a3ee",
			expectError: false,
			expected:    []byte{0xda, 0x39, 0xa3, 0xee},
		},
		{
			name:        "with 0x prefix",
			input:       "0xda39a3ee",
			expectError: false,
			expected:    []byte{0xda, 0x39, 0xa3, 0xee},
		},
		{
			name:        "with 0X prefix",
			input:       "0Xda39a3ee",
			expectError: false,
			expected:    []byte{0xda, 0x39, 0xa3, 0xee},
		},
		{
			name:        "uppercase hex",
			input:       "DA39A3EE",
			expectError: false,
			expected:    []byte{0xda, 0x39, 0xa3, 0xee},
		},
		{
			name:        "empty string",
			input:       "",
			expectError: true,
			expected:    nil,
		},
		{
			name:        "odd length",
			input:       "da39a3e",
			expectError: true,
			expected:    nil,
		},
		{
			name:        "invalid character",
			input:       "da39g3ee",
			expectError: true,
			expected:    nil,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := ParseHash(tt.input)
			
			if tt.expectError {
				if err == nil {
					t.Errorf("ParseHash(%s) expected error, got nil", tt.input)
				}
			} else {
				if err != nil {
					t.Errorf("ParseHash(%s) unexpected error: %v", tt.input, err)
				}
				if len(result) != len(tt.expected) {
					t.Errorf("ParseHash(%s) length = %d, want %d", tt.input, len(result), len(tt.expected))
				}
				for i, b := range result {
					if i < len(tt.expected) && b != tt.expected[i] {
						t.Errorf("ParseHash(%s)[%d] = %x, want %x", tt.input, i, b, tt.expected[i])
					}
				}
			}
		})
	}
}

func TestExtractHue(t *testing.T) {
	// Test with a known hash to ensure consistent behavior with JavaScript
	hash := "da39a3ee5e6b4b0d3255bfef95601890afd80709"
	hue := ExtractHue(hash)
	
	// Verify it's in [0,1] range
	if hue < 0.0 || hue > 1.0 {
		t.Errorf("ExtractHue(%s) = %f, want value in [0,1] range", hash, hue)
	}
	
	// Verify it matches manual calculation
	expectedValue, err := parseHex(hash, -7, 0)
	if err != nil {
		t.Fatalf("parseHex failed: %v", err)
	}
	expectedHue := float64(expectedValue) / 0xfffffff
	if hue != expectedHue {
		t.Errorf("ExtractHue(%s) = %f, want %f", hash, hue, expectedHue)
	}
	
	// Test error cases - should return 0.0 for JavaScript compatibility
	t.Run("invalid hash", func(t *testing.T) {
		invalidHash := "invalid-hash-with-non-hex-chars"
		hue := ExtractHue(invalidHash)
		if hue != 0.0 {
			t.Errorf("ExtractHue with invalid hash should return 0.0, got %f", hue)
		}
	})
	
	t.Run("short hash", func(t *testing.T) {
		shortHash := "short"
		hue := ExtractHue(shortHash)
		if hue != 0.0 {
			t.Errorf("ExtractHue with short hash should return 0.0, got %f", hue)
		}
	})
}

func TestExtractShapeIndex(t *testing.T) {
	hash := "da39a3ee5e6b4b0d3255bfef95601890afd80709"
	
	tests := []struct {
		position int
		expected int
	}{
		{1, 0xa}, // 'a' at position 1
		{2, 0x3}, // '3' at position 2
		{4, 0xa}, // 'a' at position 4
	}
	
	for _, tt := range tests {
		result := ExtractShapeIndex(hash, tt.position)
		if result != tt.expected {
			t.Errorf("ExtractShapeIndex(%s, %d) = %d, want %d", hash, tt.position, result, tt.expected)
		}
	}
	
	// Test error cases - should return 0 for JavaScript compatibility
	t.Run("invalid hash", func(t *testing.T) {
		invalidHash := "invalid-hash-with-non-hex-chars"
		result := ExtractShapeIndex(invalidHash, 0)
		if result != 0 {
			t.Errorf("ExtractShapeIndex with invalid hash should return 0, got %d", result)
		}
	})
	
	t.Run("out of bounds position", func(t *testing.T) {
		result := ExtractShapeIndex(hash, 100)
		if result != 0 {
			t.Errorf("ExtractShapeIndex with out of bounds position should return 0, got %d", result)
		}
	})
}

func TestExtractColorIndex(t *testing.T) {
	hash := "da39a3ee5e6b4b0d3255bfef95601890afd80709"
	
	// Test modulo behavior
	availableColors := 5
	position := 8
	rawValue, err := parseHex(hash, position, 1)
	if err != nil {
		t.Fatalf("parseHex failed: %v", err)
	}
	expected := rawValue % availableColors
	
	result := ExtractColorIndex(hash, position, availableColors)
	if result != expected {
		t.Errorf("ExtractColorIndex(%s, %d, %d) = %d, want %d", hash, position, availableColors, result, expected)
	}
	
	// Test with zero availableColors
	result = ExtractColorIndex(hash, position, 0)
	if result != rawValue {
		t.Errorf("ExtractColorIndex(%s, %d, 0) = %d, want %d", hash, position, result, rawValue)
	}
	
	// Test error cases - should return 0 for JavaScript compatibility
	t.Run("invalid hash", func(t *testing.T) {
		invalidHash := "invalid-hash-with-non-hex-chars"
		result := ExtractColorIndex(invalidHash, 0, 5)
		if result != 0 {
			t.Errorf("ExtractColorIndex with invalid hash should return 0, got %d", result)
		}
	})
	
	t.Run("out of bounds position", func(t *testing.T) {
		result := ExtractColorIndex(hash, 100, 5)
		if result != 0 {
			t.Errorf("ExtractColorIndex with out of bounds position should return 0, got %d", result)
		}
	})
}

func TestExtractRotation(t *testing.T) {
	hash := "da39a3ee5e6b4b0d3255bfef95601890afd80709"
	
	tests := []struct {
		position int
		expected int
	}{
		{0, 0xd}, // 'd' at position 0
		{1, 0xa}, // 'a' at position 1
		{5, 0x3}, // '3' at position 5
	}
	
	for _, tt := range tests {
		result := ExtractRotation(hash, tt.position)
		if result != tt.expected {
			t.Errorf("ExtractRotation(%s, %d) = %d, want %d", hash, tt.position, result, tt.expected)
		}
	}
	
	// Test error cases - should return 0 for JavaScript compatibility
	t.Run("invalid hash", func(t *testing.T) {
		invalidHash := "invalid-hash-with-non-hex-chars"
		result := ExtractRotation(invalidHash, 0)
		if result != 0 {
			t.Errorf("ExtractRotation with invalid hash should return 0, got %d", result)
		}
	})
	
	t.Run("out of bounds position", func(t *testing.T) {
		result := ExtractRotation(hash, 100)
		if result != 0 {
			t.Errorf("ExtractRotation with out of bounds position should return 0, got %d", result)
		}
	})
}