package geodb

import (
	"fmt"
	"net"
	"strconv"
	"strings"
)

type trie_Node struct {
	childrens [2]*trie_Node
	ends      bool
	cc        string
}

// Initializing the root of the trie
type trie struct {
	root *trie_Node
}

func ipToBitString(ip string) string {
	// Parse the IP address string into a net.IP object
	parsedIP := net.ParseIP(ip)

	// Convert the IP address to a 4-byte slice
	ipBytes := parsedIP.To4()

	// Convert each byte in the IP address to its 8-bit binary representation
	var result []string
	for _, b := range ipBytes {
		result = append(result, fmt.Sprintf("%08b", b))
	}

	// Join the binary representation of each byte with dots to form the final bit string
	return strings.Join(result, "")
}

func bitStringToIp(bitString string) string {
	// Split the bit string into four 8-bit segments
	segments := []string{
		bitString[:8],
		bitString[8:16],
		bitString[16:24],
		bitString[24:32],
	}

	// Convert each segment to its decimal equivalent
	var decimalSegments []int
	for _, s := range segments {
		i, _ := strconv.ParseInt(s, 2, 64)
		decimalSegments = append(decimalSegments, int(i))
	}

	// Join the decimal segments with dots to form the IP address string
	return fmt.Sprintf("%d.%d.%d.%d", decimalSegments[0], decimalSegments[1], decimalSegments[2], decimalSegments[3])
}

// inititlaizing a new trie
func newTrie() *trie {
	t := new(trie)
	t.root = new(trie_Node)
	return t
}

// Passing words to trie
func (t *trie) insert(ipAddr string, cc string) {
	word := ipToBitString(ipAddr)
	current := t.root
	for _, wr := range word {
		index := wr - '0'
		if current.childrens[index] == nil {
			current.childrens[index] = &trie_Node{
				childrens: [2]*trie_Node{},
				ends:      false,
				cc:        cc,
			}
		}
		current = current.childrens[index]
	}
	current.ends = true
}

func isReservedIP(ip string) bool {
	parsedIP := net.ParseIP(ip)
	if parsedIP == nil {
		return false
	}
	// Check if the IP address is a loopback address
	if parsedIP.IsLoopback() {
		return true
	}
	// Check if the IP address is in the link-local address range
	if parsedIP.IsLinkLocalUnicast() || parsedIP.IsLinkLocalMulticast() {
		return true
	}
	// Check if the IP address is in the private address ranges
	privateRanges := []*net.IPNet{
		{IP: net.ParseIP("10.0.0.0"), Mask: net.CIDRMask(8, 32)},
		{IP: net.ParseIP("172.16.0.0"), Mask: net.CIDRMask(12, 32)},
		{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)},
	}
	for _, r := range privateRanges {
		if r.Contains(parsedIP) {
			return true
		}
	}
	// If the IP address is not a reserved address, return false
	return false
}

// Initializing the search for word in node
func (t *trie) search(ipAddr string) string {
	if isReservedIP(ipAddr) {
		return ""
	}
	word := ipToBitString(ipAddr)
	current := t.root
	for _, wr := range word {
		index := wr - '0'
		if current.childrens[index] == nil {
			return current.cc
		}
		current = current.childrens[index]
	}
	if current.ends {
		return current.cc
	}

	//Not found
	return ""
}