package utils

import (
	"encoding/binary"
	"encoding/json"
	"fmt"
	"hash/fnv"
	"math"
	"strconv"

	"github.com/golang-jwt/jwt/v5"
)

type WbAuthData struct {
	Token string `json:"token"`
}

func NewWbAuthData(token string) WbAuthData {
	return WbAuthData{
		Token: token,
	}
}

func DecodeWildberriesJwt(token []byte) (WbAuthData, jwt.MapClaims, error) {
	var authData WbAuthData
	err := json.Unmarshal(token, &authData)
	if err != nil {
		return authData, nil, fmt.Errorf("failed to unmarshal JWT: %w", err)
	}
	claims := jwt.MapClaims{}
	_, _, err = jwt.NewParser().ParseUnverified(authData.Token, claims)
	if err != nil {
		return authData, nil, fmt.Errorf("invalid JWT: %w", err)
	}

	return authData, claims, nil
}

func ToPtrs[T any](s []T) []*T {
	res := make([]*T, len(s))
	for i := range s {
		res[i] = &s[i]
	}
	return res
}

func DerefSlice[T any](s []*T) []T {
	res := make([]T, len(s))
	for i := range s {
		if s[i] != nil {
			res[i] = *s[i]
		}
	}
	return res
}

type Hashable interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
		~string | ~bool | ~float32 | ~float64
}

func HashArray[T Hashable](arr []T) (string, error) {
	h := fnv.New64a()
	buf := make([]byte, 8)

	for _, v := range arr {
		var b []byte

		switch val := any(v).(type) {
		case int:
			binary.LittleEndian.PutUint64(buf, uint64(val))
			b = buf
		case int8:
			buf[0] = byte(val)
			b = buf[:1]
		case int16:
			binary.LittleEndian.PutUint16(buf, uint16(val))
			b = buf[:2]
		case int32:
			binary.LittleEndian.PutUint32(buf, uint32(val))
			b = buf[:4]
		case int64:
			binary.LittleEndian.PutUint64(buf, uint64(val))
			b = buf
		case uint:
			binary.LittleEndian.PutUint64(buf, uint64(val))
			b = buf
		case uint8:
			buf[0] = val
			b = buf[:1]
		case uint16:
			binary.LittleEndian.PutUint16(buf, val)
			b = buf[:2]
		case uint32:
			binary.LittleEndian.PutUint32(buf, val)
			b = buf[:4]
		case uint64:
			binary.LittleEndian.PutUint64(buf, val)
			b = buf
		case float32:
			binary.LittleEndian.PutUint32(buf[:4], math.Float32bits(val))
			b = buf[:4]
		case float64:
			binary.LittleEndian.PutUint64(buf, math.Float64bits(val))
			b = buf
		case bool:
			if val {
				buf[0] = 1
			} else {
				buf[0] = 0
			}
			b = buf[:1]
		case string:
			b = []byte(val)
		default:
			return "", fmt.Errorf("unsupported type: %T", val)
		}

		if _, err := h.Write(b); err != nil {
			return "", fmt.Errorf("failed to write to hash: %w", err)
		}
	}

	return fmt.Sprintf("%x", h.Sum64()), nil
}

func IsDigit(v string) (bool, *int64) {
	if val, err := strconv.ParseInt(v, 10, 64); err == nil {
		return true, &val
	}
	return false, nil
}
