package jdenticon

import (
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/kevin/go-jdenticon/internal/util"
)

// ComputeHash computes a SHA-1 hash for any value and returns it as a hexadecimal string.
// This function mimics the JavaScript version's behavior for compatibility.
func ComputeHash(value interface{}) string {
	var input string
	
	// Handle different input types, converting to string like JavaScript version
	switch v := value.(type) {
	case nil:
		input = ""
	case string:
		input = v
	case []byte:
		input = string(v)
	case int:
		input = strconv.Itoa(v)
	case int64:
		input = strconv.FormatInt(v, 10)
	case float64:
		input = fmt.Sprintf("%g", v)
	default:
		// Convert to string using fmt.Sprintf for other types
		input = fmt.Sprintf("%v", v)
	}
	
	// Compute SHA-1 hash using Go's crypto/sha1 package
	h := sha1.New()
	h.Write([]byte(input))
	hash := h.Sum(nil)
	
	// Convert to hexadecimal string (lowercase to match JavaScript)
	return fmt.Sprintf("%x", hash)
}

// HashValue is a convenience function that wraps ComputeHash for string inputs.
// Kept for backward compatibility.
func HashValue(value string) string {
	return ComputeHash(value)
}

// IsValidHash checks if a string is a valid hash for Jdenticon.
// It must be a hexadecimal string with at least 11 characters.
func IsValidHash(hashCandidate string) bool {
	return util.IsValidHash(hashCandidate)
}

// isValidHash is a private wrapper for backward compatibility with existing tests
func isValidHash(hashCandidate string) bool {
	return IsValidHash(hashCandidate)
}

// parseHex extracts a value from a hex string at a specific position.
// This function is used to deterministically extract shape and color information
// from the hash string, matching the JavaScript implementation.
// When octets is 0 or negative, it reads from startPosition to the end of the string.
func parseHex(hash string, startPosition int, octets int) (int, error) {
	return util.ParseHex(hash, startPosition, octets)
}

// ParseHex provides a public API that matches the JavaScript parseHex function exactly.
// It extracts a hexadecimal value from the hash string at the specified position.
// If octets is not provided or is <= 0, it reads from the position to the end of the string.
// Returns 0 on error to maintain compatibility with the JavaScript implementation.
func ParseHex(hash string, startPosition int, octets ...int) int {
	octetCount := 0
	if len(octets) > 0 {
		octetCount = octets[0]
	}
	result, err := parseHex(hash, startPosition, octetCount)
	if err != nil {
		return 0 // Maintain JavaScript compatibility: return 0 on error
	}
	return result
}

// ParseHash converts a hexadecimal hash string into a byte array for further processing.
// It validates the input hash string and handles common prefixes like "0x".
// Returns an error if the hash contains invalid hexadecimal characters.
func ParseHash(hash string) ([]byte, error) {
	if hash == "" {
		return nil, errors.New("hash string cannot be empty")
	}
	
	// Remove "0x" prefix if present
	cleanHash := strings.TrimPrefix(hash, "0x")
	cleanHash = strings.TrimPrefix(cleanHash, "0X")
	
	// Validate hash length (must be even for proper byte conversion)
	if len(cleanHash)%2 != 0 {
		return nil, errors.New("hash string must have even length")
	}
	
	// Decode hex string to bytes
	bytes, err := hex.DecodeString(cleanHash)
	if err != nil {
		return nil, fmt.Errorf("invalid hexadecimal string: %w", err)
	}
	
	return bytes, nil
}

// ExtractInt extracts a specific number of bits from a hash byte array and converts them to an integer.
// The index parameter specifies the starting position (negative values count from the end).
// The bits parameter specifies how many bits to extract.
func ExtractInt(hash []byte, index, bits int) int {
	if len(hash) == 0 || bits <= 0 {
		return 0
	}
	
	// Handle negative indices (count from end)
	if index < 0 {
		index = len(hash) + index
	}
	
	// Ensure index is within bounds
	if index < 0 || index >= len(hash) {
		return 0
	}
	
	// Calculate how many bytes we need to read
	bytesNeeded := (bits + 7) / 8 // Round up to nearest byte
	
	// Ensure we don't read past the end of the array
	if index+bytesNeeded > len(hash) {
		bytesNeeded = len(hash) - index
	}
	
	if bytesNeeded <= 0 {
		return 0
	}
	
	// Extract bytes and convert to integer
	var result int
	for i := 0; i < bytesNeeded; i++ {
		if index+i < len(hash) {
			result = (result << 8) | int(hash[index+i])
		}
	}
	
	// Mask to only include the requested number of bits
	if bits < 64 {
		mask := (1 << bits) - 1
		result &= mask
	}
	
	return result
}

// ExtractFloat extracts a specific number of bits from a hash byte array and converts them to a float64 value between 0 and 1.
// The value is normalized by dividing by the maximum possible value for the given number of bits.
func ExtractFloat(hash []byte, index, bits int) float64 {
	if bits <= 0 {
		return 0.0
	}
	
	// Extract integer value
	intValue := ExtractInt(hash, index, bits)
	
	// Calculate maximum possible value for the given number of bits
	maxValue := (1 << bits) - 1
	if maxValue == 0 {
		return 0.0
	}
	
	// Normalize to [0,1] range
	return float64(intValue) / float64(maxValue)
}

// ExtractHue extracts the hue value from a hash string using the same algorithm as the JavaScript version.
// This is a convenience function that extracts the last 7 characters and normalizes to [0,1] range.
// Returns 0.0 on error to maintain compatibility with the JavaScript implementation.
func ExtractHue(hash string) float64 {
	hueValue, err := parseHex(hash, -7, 0) // Read from -7 to end
	if err != nil {
		return 0.0 // Maintain JavaScript compatibility: return 0.0 on error
	}
	return float64(hueValue) / 0xfffffff
}

// ExtractShapeIndex extracts a shape index from the hash at the specified position.
// This is a convenience function that matches the JavaScript shape selection logic.
// Returns 0 on error to maintain compatibility with the JavaScript implementation.
func ExtractShapeIndex(hash string, position int) int {
	result, err := parseHex(hash, position, 1)
	if err != nil {
		return 0 // Maintain JavaScript compatibility: return 0 on error
	}
	return result
}

// ExtractRotation extracts a rotation value from the hash at the specified position.
// This is a convenience function that matches the JavaScript rotation logic.
// Returns 0 on error to maintain compatibility with the JavaScript implementation.
func ExtractRotation(hash string, position int) int {
	result, err := parseHex(hash, position, 1)
	if err != nil {
		return 0 // Maintain JavaScript compatibility: return 0 on error
	}
	return result
}

// ExtractColorIndex extracts a color index from the hash at the specified position.
// This is a convenience function that matches the JavaScript color selection logic.
// Returns 0 on error to maintain compatibility with the JavaScript implementation.
func ExtractColorIndex(hash string, position int, availableColors int) int {
	value, err := parseHex(hash, position, 1)
	if err != nil {
		return 0 // Maintain JavaScript compatibility: return 0 on error
	}
	if availableColors > 0 {
		return value % availableColors
	}
	return value
}