package security

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"github.com/DonovanKelly/sugar-n-spice/set"
	"log"
	"math/big"
	r "math/rand"
	"time"
)

var (
	ErrFisherYatesShuffle       = errors.New("unable to shuffle array")
	ErrRandomBytes              = errors.New("random bytes error")
	ErrRandNonRepeatingUint64   = errors.New("list length must be less than 2^32")
	ErrParseHexString           = errors.New("parse hex string error")
	ErrMatrixTranspose          = errors.New("matrix cannot be nil or empty")
	ErrListToMatrixNotDivisible = errors.New("list to matrix not possible")
	ErrElementNotInArray        = errors.New("element not in array")
	ErrDecodeBase64Str          = errors.New("decode base64 err")
	ErrRandNonRepeatingInt      = errors.New("list length must be less than 2^31")
	ErrXorLengthMismatch        = errors.New("xor length mismatch")
)

func fisherYatesShuffle[T any](b *[]T) error {
	for i := len(*b) - 1; i > 0; i-- {
		bigJ, err := rand.Int(rand.Reader, big.NewInt(int64(i+1)))
		if err != nil {
			log.Print("fisher yates shuffle error: ", err)
			return ErrFisherYatesShuffle
		}
		j := bigJ.Int64()
		(*b)[i], (*b)[j] = (*b)[j], (*b)[i]
	}
	return nil
}

func FisherYatesShuffle[T any](b *[]T) error {
	return fisherYatesShuffle(b)
}

func RandomBytes(n int) ([]byte, error) {
	b := make([]byte, n)
	_, err := rand.Read(b)
	if err != nil {
		log.Print("error in random bytes: ", err)
		return nil, ErrRandomBytes
	}
	return b, nil
}

func RandomPermutation(n int) ([]int, error) {
	perm := IdentityArray(n)
	err := fisherYatesShuffle(&perm)
	if err != nil {
		return nil, err
	}
	return perm, nil
}

func GenerateRandomUInt64() (uint64, error) {
	randBytes, err := RandomBytes(8)
	if err != nil {
		return 0, err
	}
	val := binary.LittleEndian.Uint64(randBytes)
	return val, nil
}

func GenerateRandomInt() (int, error) {
	randBytes, err := RandomBytes(8)
	if err != nil {
		return 0, err
	}
	val := int(binary.LittleEndian.Uint64(randBytes) & 0x7FFFFFFFFFFFFFFF) // Ensure it's positive
	return val, nil
}

func GenerateRandomNonRepeatingUint64(listLen int) ([]uint64, error) {
	if listLen > int(1)<<32 {
		return nil, ErrRandNonRepeatingUint64
	}
	listSet := make(set.Set[uint64])
	for {
		if listSet.Size() == listLen {
			break
		}
		randNum, err := GenerateRandomUInt64()
		if err != nil {
			return nil, err
		}
		listSet.Add(randNum)
	}

	data := listSet.ToSlice()
	return data, nil
}

func GenerateRandomNonRepeatingInt(listLen int) ([]int, error) {
	if listLen > int(1)<<31 {
		return nil, ErrRandNonRepeatingInt
	}
	listSet := make(set.Set[int])
	for {
		if listSet.Size() == listLen {
			break
		}
		randNum, err := GenerateRandomInt()
		if err != nil {
			return nil, err
		}
		listSet.Add(randNum)
	}

	data := listSet.ToSlice()
	return data, nil
}

func XorLists(l0 []uint64, l1 []uint64) ([]uint64, error) {
	if len(l0) != len(l1) {
		log.Printf("list len mismatch %d, %d", len(l0), len(l1))
		return nil, ErrXorLengthMismatch
	}

	xorList := make([]uint64, len(l0))
	for idx := 0; idx < len(l0); idx++ {
		xorList[idx] = l0[idx] ^ l1[idx]
	}
	return xorList, nil
}

func EncodeBase64Str(data []uint64) string {
	dataBytes := Uint64ArrToByteArr(data)
	encoded := base64.StdEncoding.EncodeToString(dataBytes)
	return encoded
}

func DecodeBase64Str(encoded string) ([]uint64, error) {
	dataBytes, err := base64.StdEncoding.DecodeString(encoded)
	if err != nil {
		log.Print("error decoding base64 str: ", err)
		return nil, ErrDecodeBase64Str
	}
	data := ByteArrToUint64Arr(dataBytes)
	return data, nil
}

func Uint64ArrToByteArr(intArr []uint64) []byte {
	byteArr := make([]byte, len(intArr)*8)
	for idx, val := range intArr {
		startIdx := idx * 8
		endIdx := (idx + 1) * 8
		binary.LittleEndian.PutUint64(byteArr[startIdx:endIdx], val)
	}
	return byteArr
}

func IntArrToByteArr(intArr []int) []byte {
	byteArr := make([]byte, len(intArr)*4)
	for idx, val := range intArr {
		uval := uint32(val)
		startIdx := idx * 4
		endIdx := (idx + 1) * 4
		binary.LittleEndian.PutUint32(byteArr[startIdx:endIdx], uval)
	}
	return byteArr
}

func ByteArrToUint64Arr(byteArr []byte) []uint64 {
	intArr := make([]uint64, len(byteArr)/8)
	for idx := 0; idx < len(intArr); idx++ {
		startIdx := idx * 8
		endIdx := (idx + 1) * 8
		intArr[idx] = binary.LittleEndian.Uint64(byteArr[startIdx:endIdx])
	}
	return intArr
}

func ByteArrToIntArr(byteArr []byte) []int {
	intArr := make([]int, len(byteArr)/4)
	for idx := 0; idx < len(intArr); idx++ {
		startIdx := idx * 4
		endIdx := (idx + 1) * 4
		uval := binary.LittleEndian.Uint32(byteArr[startIdx:endIdx])
		intArr[idx] = int(uval)
	}
	return intArr
}

func IndexOf[T uint64 | int](arr []T, el T) (int, error) {
	for idx, val := range arr {
		if val == el {
			return idx, nil
		}
	}
	return -1, ErrElementNotInArray
}

func IdentityArray(arrLen int) []int {
	identityArr := make([]int, arrLen)

	for idx := range identityArr {
		identityArr[idx] = idx
	}
	return identityArr
}

func ListToMatrix(listArr []int, numbCols int) ([][]int, error) {
	if len(listArr)%numbCols != 0 {
		log.Printf("Array is not evenly divisible by number of columns: %d mod %d = %d", len(listArr), numbCols, len(listArr)%numbCols)
		return nil, ErrListToMatrixNotDivisible
	}
	numbRows := len(listArr) / numbCols
	matrix := make([][]int, numbRows)
	for idx := range matrix {
		startIdx := idx * numbCols
		endIdx := (idx + 1) * numbCols
		matrix[idx] = listArr[startIdx:endIdx]
	}
	return matrix, nil
}

func MatrixTranspose(matrix [][]int) ([][]int, error) {
	if matrix == nil || len(matrix) == 0 {
		log.Print("can't transpose nil or zero len matrix")
		return nil, ErrMatrixTranspose
	}

	rows := len(matrix)
	cols := len((matrix)[0])

	// Check if the matrix is not rectangular
	for _, row := range matrix {
		if len(row) != cols {
			log.Print("all rows must have the same number of columns")
			return nil, ErrMatrixTranspose
		}
	}

	transposed := make([][]int, cols)
	for i := range transposed {
		transposed[i] = make([]int, rows)
	}

	for i := 0; i < rows; i++ {
		for j := 0; j < cols; j++ {
			transposed[j][i] = (matrix)[i][j]
		}
	}

	return transposed, nil
}

func MatrixToList(matrix [][]int) []int {
	var flat []int
	for _, row := range matrix {
		flat = append(flat, row...)
	}
	return flat
}

func Choice[T any](items []T) T {
	r.Seed(time.Now().UnixNano()) // Seed the random number generator
	return items[r.Intn(len(items))]
}

// GenerateRandomString creates a random string of a specified length.
func GenerateRandomString(length int) string {
	charset := []rune("abcdefghijklmnopqrstuvwxyz0123456789")
	b := make([]rune, length)
	for i := range b {
		b[i] = Choice[rune](charset)
	}
	return string(b)
}

func ParseHexString(hexStr string) ([]byte, error) {
	// Decode the hex string into bytes
	bytes, err := hex.DecodeString(hexStr)
	if err != nil {
		log.Print("parse hex string err: ", err)
		return nil, ErrParseHexString
	}
	return bytes, nil
}
