package utils

import (
	"context"
	"encoding/json"
	"math/big"
	"reflect"
	"strings"
	"time"

	"github.com/google/uuid"
)

// GenerateID generates a new UUID
func GenerateID() uuid.UUID {
	return uuid.New()
}

// ParseUUID parses a string to UUID
func ParseUUID(s string) (uuid.UUID, error) {
	return uuid.Parse(s)
}

// MustParseUUID parses a string to UUID, panics on error
func MustParseUUID(s string) uuid.UUID {
	id, err := uuid.Parse(s)
	if err != nil {
		panic(err)
	}
	return id
}

// IsValidUUID checks if a string is a valid UUID
func IsValidUUID(s string) bool {
	_, err := uuid.Parse(s)
	return err == nil
}

// ToJSON converts an interface to JSON bytes
func ToJSON(v interface{}) ([]byte, error) {
	return json.Marshal(v)
}

// FromJSON converts JSON bytes to an interface
func FromJSON(data []byte, v interface{}) error {
	return json.Unmarshal(data, v)
}

// NowUTC returns the current UTC time
func NowUTC() time.Time {
	return time.Now().UTC()
}

// TimePtr returns a pointer to the time
func TimePtr(t time.Time) *time.Time {
	return &t
}

// NowPtr returns a pointer to the current time
func NowPtr() *time.Time {
	now := NowUTC()
	return &now
}

// BigIntToBytes converts a big.Int to bytes (32 bytes, left-padded)
func BigIntToBytes(n *big.Int) []byte {
	if n == nil {
		return make([]byte, 32)
	}
	b := n.Bytes()
	if len(b) > 32 {
		return b[:32]
	}
	if len(b) < 32 {
		result := make([]byte, 32)
		copy(result[32-len(b):], b)
		return result
	}
	return b
}

// BytesToBigInt converts bytes to big.Int
func BytesToBigInt(b []byte) *big.Int {
	return new(big.Int).SetBytes(b)
}

// StringSliceContains checks if a string slice contains a value
func StringSliceContains(slice []string, value string) bool {
	for _, s := range slice {
		if s == value {
			return true
		}
	}
	return false
}

// StringSliceRemove removes a value from a string slice
func StringSliceRemove(slice []string, value string) []string {
	result := make([]string, 0, len(slice))
	for _, s := range slice {
		if s != value {
			result = append(result, s)
		}
	}
	return result
}

// UniqueStrings returns unique strings from a slice
func UniqueStrings(slice []string) []string {
	seen := make(map[string]struct{})
	result := make([]string, 0, len(slice))
	for _, s := range slice {
		if _, ok := seen[s]; !ok {
			seen[s] = struct{}{}
			result = append(result, s)
		}
	}
	return result
}

// TruncateString truncates a string to max length
func TruncateString(s string, maxLen int) string {
	if len(s) <= maxLen {
		return s
	}
	return s[:maxLen]
}

// SafeString returns an empty string if the pointer is nil
func SafeString(s *string) string {
	if s == nil {
		return ""
	}
	return *s
}

// StringPtr returns a pointer to the string
func StringPtr(s string) *string {
	return &s
}

// IntPtr returns a pointer to the int
func IntPtr(i int) *int {
	return &i
}

// BoolPtr returns a pointer to the bool
func BoolPtr(b bool) *bool {
	return &b
}

// IsZero checks if a value is zero/empty
func IsZero(v interface{}) bool {
	return reflect.ValueOf(v).IsZero()
}

// Coalesce returns the first non-zero value
func Coalesce[T comparable](values ...T) T {
	var zero T
	for _, v := range values {
		if v != zero {
			return v
		}
	}
	return zero
}

// MapKeys returns the keys of a map
func MapKeys[K comparable, V any](m map[K]V) []K {
	keys := make([]K, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	return keys
}

// MapValues returns the values of a map
func MapValues[K comparable, V any](m map[K]V) []V {
	values := make([]V, 0, len(m))
	for _, v := range m {
		values = append(values, v)
	}
	return values
}

// Min returns the minimum of two values
func Min[T ~int | ~int64 | ~float64](a, b T) T {
	if a < b {
		return a
	}
	return b
}

// Max returns the maximum of two values
func Max[T ~int | ~int64 | ~float64](a, b T) T {
	if a > b {
		return a
	}
	return b
}

// Clamp clamps a value between min and max
func Clamp[T ~int | ~int64 | ~float64](value, min, max T) T {
	if value < min {
		return min
	}
	if value > max {
		return max
	}
	return value
}

// ContextWithTimeout creates a context with timeout
func ContextWithTimeout(timeout time.Duration) (context.Context, context.CancelFunc) {
	return context.WithTimeout(context.Background(), timeout)
}

// MaskString masks a string showing only first and last n characters
func MaskString(s string, showChars int) string {
	if len(s) <= showChars*2 {
		return strings.Repeat("*", len(s))
	}
	return s[:showChars] + strings.Repeat("*", len(s)-showChars*2) + s[len(s)-showChars:]
}

// Retry executes a function with retries
func Retry(attempts int, sleep time.Duration, f func() error) error {
	var err error
	for i := 0; i < attempts; i++ {
		if err = f(); err == nil {
			return nil
		}
		if i < attempts-1 {
			time.Sleep(sleep)
			sleep *= 2 // Exponential backoff
		}
	}
	return err
}
