// Copyright (c) 2025 Karl Gaissmaier
// SPDX-License-Identifier: MIT

package random

import (
	"fmt"
	"math/rand"

	"net/netip"
)

// mpp is a abbreviation and panics on non masked prefixes.
//
//nolint:gochecknoglobals
var mpp = func(s string) netip.Prefix {
	pfx := netip.MustParsePrefix(s)
	if pfx == pfx.Masked() {
		return pfx
	}
	panic(fmt.Sprintf("%s is not canonicalized as %s", s, pfx.Masked()))
}

// Prefix returns a randomly generated prefix
func Prefix(prng *rand.Rand) netip.Prefix {
	if prng.Intn(2) == 1 {
		return Prefix4(prng)
	}
	return Prefix6(prng)
}

func Prefix4(prng *rand.Rand) netip.Prefix {
	bits := prng.Intn(33)
	return netip.PrefixFrom(IP4(prng), bits).Masked()
}

func Prefix6(prng *rand.Rand) netip.Prefix {
	bits := prng.Intn(129)
	return netip.PrefixFrom(IP6(prng), bits).Masked()
}

func IP4(prng *rand.Rand) netip.Addr {
	var b [4]byte
	for i := range b {
		b[i] = byte(prng.Intn(256))
	}
	return netip.AddrFrom4(b)
}

func IP6(prng *rand.Rand) netip.Addr {
	var b [16]byte
	for i := range b {
		b[i] = byte(prng.Intn(256))
	}
	return netip.AddrFrom16(b)
}

func IP(prng *rand.Rand) netip.Addr {
	if prng.Intn(2) == 1 {
		return IP4(prng)
	}
	return IP6(prng)
}

func RealWorldPrefixes4(prng *rand.Rand, n int) []netip.Prefix {
	set := make(map[netip.Prefix]struct{})
	pfxs := make([]netip.Prefix, 0, n)

	for len(set) < n {
		pfx := Prefix4(prng)

		// skip too small or too big masks
		if pfx.Bits() < 8 || pfx.Bits() > 28 {
			continue
		}

		// skip reserved/experimental ranges (e.g., 240.0.0.0/8)
		if pfx.Overlaps(mpp("240.0.0.0/8")) {
			continue
		}

		if _, ok := set[pfx]; !ok {
			set[pfx] = struct{}{}
			pfxs = append(pfxs, pfx)
		}
	}
	return pfxs
}

func RealWorldPrefixes6(prng *rand.Rand, n int) []netip.Prefix {
	set := make(map[netip.Prefix]struct{})
	pfxs := make([]netip.Prefix, 0, n)

	for len(set) < n {
		pfx := Prefix6(prng)

		// skip too small or too big masks
		if pfx.Bits() < 16 || pfx.Bits() > 56 {
			continue
		}

		// skip non global routes seen in the real world
		if !pfx.Overlaps(mpp("2000::/3")) {
			continue
		}
		if pfx.Addr().Compare(mpp("2c0f::/16").Addr()) == 1 {
			continue
		}

		if _, ok := set[pfx]; !ok {
			set[pfx] = struct{}{}
			pfxs = append(pfxs, pfx)
		}
	}
	return pfxs
}

func RealWorldPrefixes(prng *rand.Rand, n int) []netip.Prefix {
	pfxs := make([]netip.Prefix, 0, n)
	pfxs = append(pfxs, RealWorldPrefixes4(prng, n/2)...)
	pfxs = append(pfxs, RealWorldPrefixes6(prng, n-len(pfxs))...)

	prng.Shuffle(len(pfxs), func(i, j int) {
		pfxs[i], pfxs[j] = pfxs[j], pfxs[i]
	})

	return pfxs
}
