package random

import (
	"crypto/rand"
	"math/big"
	"net"
	"strings"

	"github.com/vulncheck-oss/go-exploit/output"
)

var (
	letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
	hex     = []rune("0123456789abcdef")
	digits  = []rune("0123456789")
)

// RandIntRange generates an `int` between [min,max).
func RandIntRange(rangeMin int, rangeMax int) int {
	rangeMaxBig := big.NewInt(int64(rangeMax) - int64(rangeMin))
	n, err := rand.Int(rand.Reader, rangeMaxBig)
	if err != nil {
		// if random fails the process will die anyway: https://github.com/golang/go/issues/66821
		output.PrintfFrameworkError("Random generation error: %s", err.Error())

		return 0
	}

	return int(n.Int64() + int64(rangeMin))
}

// RandPositiveInt generates a non-negative crypto-random number in the half-open interval [0,max).
func RandPositiveInt(rangeMax int) int {
	n, err := rand.Int(rand.Reader, big.NewInt(int64(rangeMax)))
	if err != nil {
		// if random fails the process will die anyway: https://github.com/golang/go/issues/66821
		output.PrintfFrameworkError("Random generation error: %s", err.Error())

		return 0
	}

	return int(n.Int64())
}

// RandLetters generates a random alpha string of length n.
func RandLetters(n int) string {
	runeSlice := make([]rune, n)
	for i := range runeSlice {
		runeSlice[i] = letters[RandPositiveInt(len(letters))]
	}

	return string(runeSlice)
}

// RandLettersNoBadChars generates a random alpha string with no bad chars of length n.
// This will return an empty string if the caller badchars all "letters".
func RandLettersNoBadChars(n int, badchars []rune) string {
	// rebuild the letters slice without the bad chars. O(n^2) implementation
	// not really sure it is worthwhile to get more fancy :shrug:
	var nobad []rune
	for _, letter := range letters {
		found := false
		for _, char := range badchars {
			if char == letter {
				found = true
			}
		}
		if !found {
			nobad = append(nobad, letter)
		}
	}

	if len(nobad) == 0 {
		return ""
	}

	runeSlice := make([]rune, n)
	for i := range runeSlice {
		runeSlice[i] = nobad[RandPositiveInt(len(nobad))]
	}

	return string(runeSlice)
}

// RandLettersRange generates a random alpha string of length [min,max).
func RandLettersRange(rangeMin int, rangeMax int) string {
	return RandLetters(RandIntRange(rangeMin, rangeMax-1))
}

// RandMAC generates a random MAC address and returns it
func RandMAC() string {
	parts := []string{}
	for range 6 {
		parts = append(parts, RandHex(2))
	}
	return strings.Join(parts, ":")
}

func RandHex(n int) string {
	runeSlice := make([]rune, n)
	for i := range runeSlice {
		runeSlice[i] = hex[RandPositiveInt(len(hex))]
	}

	return string(runeSlice)
}

// RandHexRange generates a random hex string of length [min,max).
func RandHexRange(rangeMin int, rangeMax int) string {
	return RandHex(RandIntRange(rangeMin, rangeMax-1))
}

func RandDigits(n int) string {
	runeSlice := make([]rune, n)
	for i := range runeSlice {
		runeSlice[i] = digits[RandPositiveInt(len(digits))]
	}

	// keep assigning a new digit until the first one isn't 0'
	if len(runeSlice) > 0 {
		for runeSlice[0] == '0' {
			runeSlice[0] = digits[RandPositiveInt(len(digits))]
		}
	}

	return string(runeSlice)
}

// RandDigitsRange generates a random numeric string of length [min,max).
func RandDigitsRange(rangeMin int, rangeMax int) string {
	return RandDigits(RandIntRange(rangeMin, rangeMax))
}

// RandDomain generates a random domain name with a common TLDs. The domain will be between 8 and 14 characters.
func RandDomain() string {
	return strings.ToLower(RandLettersRange(4, 10) + "." + CommonTLDs[RandPositiveInt(len(CommonTLDs))])
}

// RandEmail generates a random email address using common domain TLDs. The largest size of the
// generated domain will be 23 characters and smallest will be 13 characters. The goal is not to
// generate a set of RFC valid domains, but simple lower case emails that are valid for most
// automated account registration or usage, so these might be "too simple" or for some uses.
func RandEmail() string {
	return strings.ToLower(RandLettersRange(4, 8) + "@" + RandDomain())
}

// CommonTLDs contains the 3 most common DNS TLDs.
var CommonTLDs = []string{
	"com",
	"org",
	"net",
}

// RandIPv4 generates a random IPv4 address.
func RandIPv4() net.IP {
	return net.IPv4(
		byte(RandIntRange(1, 256)),
		byte(RandIntRange(1, 256)),
		byte(RandIntRange(1, 256)),
		byte(RandIntRange(1, 256)),
	)
}

// RandIPv4Private Generates a random private IPv4 address.
func RandIPv4Private() net.IP {
	n, _ := rand.Int(rand.Reader, big.NewInt(3))
	switch n.Int64() {
	case 0:
		return net.IPv4(
			10,
			byte(RandIntRange(1, 256)),
			byte(RandIntRange(1, 256)),
			byte(RandIntRange(1, 256)),
		)
	case 1:
		return net.IPv4(
			172,
			byte(RandIntRange(16, 32)),
			byte(RandIntRange(1, 256)),
			byte(RandIntRange(1, 256)),
		)
	default:
		return net.IPv4(
			192,
			168,
			byte(RandIntRange(1, 256)),
			byte(RandIntRange(1, 256)),
		)
	}
}

// RandomIPv4Loopback generates IPv4 Loopback address
func RandIPv4Loopback() net.IP {
	return net.IPv4(
		127,
		byte(RandIntRange(1, 256)),
		byte(RandIntRange(1, 256)),
		byte(RandIntRange(1, 256)),
	)
}

// RandIPv6 generates a random IPv6 address.
func RandIPv6() net.IP {
	ip := make(net.IP, net.IPv6len)
	for i := range net.IPv6len {
		ip[i] = byte(RandIntRange(1, 256))
	}

	return ip
}
