package utils

import (
	"encoding/binary"
	"math"
	"math/big"
	"net"
	"strconv"
	"strings"
)

// ===========================================================================
// =============================   ParseIP   =================================
// ===========================================================================

// ParseIP parses a CIDR, an IP range string (e.g., "xxx-xxx"), or a single IP into a slice of actual IPs.
// Supports both IPv4 and IPv6.
func ParseIP(ip string) []string {
	var availableIPs []string

	ip = strings.TrimRight(ip, "/")
	if strings.Contains(ip, "/") {
		// Handle single IP case in CIDR format
		if strings.HasSuffix(ip, "/32") || strings.HasSuffix(ip, "/128") {
			availableIPs = append(availableIPs, strings.Split(ip, "/")[0])
		} else {
			availableIPs = getAvailableIP(ip)
		}
	} else if strings.Contains(ip, "-") {
		ipRange := strings.SplitN(ip, "-", 2)
		if len(ipRange) == 2 {
			availableIPs = getAvailableIPRange(ipRange[0], ipRange[1])
		}
	} else {
		availableIPs = append(availableIPs, ip)
	}

	return availableIPs
}

// getAvailableIPRange generates all IPs between the start and end IP addresses (inclusive).
// Supports both IPv4 and IPv6.
func getAvailableIPRange(ipStart, ipEnd string) []string {
	var availableIPs []string

	startIP := net.ParseIP(ipStart)
	endIP := net.ParseIP(ipEnd)
	if startIP == nil || endIP == nil {
		return availableIPs
	}

	// Determine if IPv4 or IPv6
	if startIP.To4() != nil && endIP.To4() != nil {
		startIP = startIP.To4()
		endIP = endIP.To4()
		startIPNum := ip4ToInt(startIP)
		endIPNum := ip4ToInt(endIP)
		for ipNum := new(big.Int).Set(startIPNum); ipNum.Cmp(endIPNum) <= 0; ipNum.Add(ipNum, big.NewInt(1)) {
			availableIPs = append(availableIPs, intToIP4(ipNum).String())
		}
	} else if startIP.To16() != nil && endIP.To16() != nil {
		startIP = startIP.To16()
		endIP = endIP.To16()
		startIPNum := ip6ToInt(startIP)
		endIPNum := ip6ToInt(endIP)
		for ipNum := new(big.Int).Set(startIPNum); ipNum.Cmp(endIPNum) <= 0; ipNum.Add(ipNum, big.NewInt(1)) {
			availableIPs = append(availableIPs, intToIP6(ipNum).String())
		}
	}

	return availableIPs
}

// getAvailableIP calculates all available IPs in a given CIDR.
// Supports both IPv4 and IPv6.
func getAvailableIP(ipAndMask string) []string {
	var availableIPs []string

	ipAndMask = strings.TrimSpace(ipAndMask)
	ipAndMask = iPAddressToCIDR(ipAndMask)

	_, ipnet, err := net.ParseCIDR(ipAndMask)
	if err != nil || ipnet == nil {
		return availableIPs
	}

	firstIP, lastIP := networkRange(ipnet)
	if firstIP == nil || lastIP == nil {
		return availableIPs
	}

	// IPv4
	if firstIP.To4() != nil {
		startIPNum := ip4ToInt(firstIP)
		endIPNum := ip4ToInt(lastIP)
		// Exclude network and broadcast addresses if possible
		for ipNum := new(big.Int).Add(startIPNum, big.NewInt(1)); ipNum.Cmp(endIPNum) < 0; ipNum.Add(ipNum, big.NewInt(1)) {
			availableIPs = append(availableIPs, intToIP4(ipNum).String())
		}
	} else if firstIP.To16() != nil {
		// IPv6: no broadcast, so include all except network address
		startIPNum := ip6ToInt(firstIP)
		endIPNum := ip6ToInt(lastIP)
		for ipNum := new(big.Int).Set(startIPNum); ipNum.Cmp(endIPNum) <= 0; ipNum.Add(ipNum, big.NewInt(1)) {
			availableIPs = append(availableIPs, intToIP6(ipNum).String())
		}
	}

	return availableIPs
}

// ip4ToInt converts an IPv4 address to a big.Int.
func ip4ToInt(ip net.IP) *big.Int {
	ip = ip.To4()
	if ip == nil {
		return big.NewInt(0)
	}
	return big.NewInt(0).SetUint64(uint64(binary.BigEndian.Uint32(ip)))
}

// intToIP4 converts a big.Int to an IPv4 address.
func intToIP4(n *big.Int) net.IP {
	maxIPv4 := big.NewInt(int64(math.MaxUint32))
	if n.Cmp(maxIPv4) > 0 {
		n = maxIPv4
	}

	// Get 4-byte representation
	b := n.Bytes()
	if len(b) < 4 {
		// Pad with leading zeros if necessary
		padded := make([]byte, 4)
		copy(padded[4-len(b):], b)
		b = padded
	} else if len(b) > 4 {
		// Trim to last 4 bytes if it's longer (shouldn't happen due to clamp)
		b = b[len(b)-4:]
	}

	return net.IP(b)
}

// ip6ToInt converts an IPv6 address to a big.Int.
func ip6ToInt(ip net.IP) *big.Int {
	ip = ip.To16()
	if ip == nil {
		return big.NewInt(0)
	}
	return big.NewInt(0).SetBytes(ip)
}

// intToIP6 converts a big.Int to an IPv6 address.
func intToIP6(n *big.Int) net.IP {
	b := n.Bytes()
	if len(b) < 16 {
		pad := make([]byte, 0, 16-len(b))
		b = append(pad, b...)
	}
	return net.IP(b)
}

// iPAddressToCIDR converts an IP address with a subnet mask to CIDR format.
// Only supports IPv4 mask notation (e.g., "192.168.1.1/255.255.255.0").
func iPAddressToCIDR(ipAddress string) string {
	if strings.Contains(ipAddress, "/") {
		parts := strings.Split(ipAddress, "/")
		ip := parts[0]
		mask := parts[1]
		if strings.Contains(mask, ".") {
			mask = iPMaskStringToCIDR(mask)
		}
		return ip + "/" + mask
	}
	return ipAddress
}

// iPMaskStringToCIDR converts a subnet mask string (e.g., "255.255.255.0") to a CIDR prefix length.
func iPMaskStringToCIDR(netmask string) string {
	parts := strings.Split(netmask, ".")
	if len(parts) != 4 {
		return "0"
	}
	maskBytes := make([]byte, 4)
	for i, part := range parts {
		val, _ := strconv.Atoi(part)
		maskBytes[i] = byte(val)
	}
	mask := net.IPv4Mask(maskBytes[0], maskBytes[1], maskBytes[2], maskBytes[3])
	ones, _ := mask.Size()
	return strconv.Itoa(ones)
}

// networkRange calculates the first and last IP in a given network.
// Supports both IPv4 and IPv6.
func networkRange(network *net.IPNet) (net.IP, net.IP) {
	netIP := network.IP
	mask := network.Mask
	if netIP == nil || mask == nil {
		return nil, nil
	}
	ipLen := len(netIP)
	if ipLen == net.IPv4len {
		netIP = netIP.To4()
	} else if ipLen == net.IPv6len {
		netIP = netIP.To16()
	}
	if netIP == nil {
		return nil, nil
	}
	startIP := make(net.IP, len(netIP))
	copy(startIP, netIP.Mask(mask))
	endIP := make(net.IP, len(startIP))
	for i := range startIP {
		endIP[i] = startIP[i] | ^mask[i]
	}
	return startIP, endIP
}

// ===========================================================================
// =============================   IsLocalhostIP   ===========================
// ===========================================================================

// IsLocalhostIP checks if the given IP address string (ipStr) is bound to any local network interface.
// It returns true if the IP is found on any interface, false otherwise.
// This function parses the input string as an IP address, iterates over all network interfaces on the host,
// and checks if any of the interface addresses match the target IP.
func IsLocalhostIP(ipStr string) bool {
	targetIP := net.ParseIP(ipStr)
	if targetIP == nil {
		// The input string is not a valid IP address.
		return false
	}
	ifaces, err := net.Interfaces()
	if err != nil {
		// Failed to retrieve network interfaces.
		return false
	}
	for _, iface := range ifaces {
		addrs, err := iface.Addrs()
		if err != nil {
			// Skip this interface if its addresses cannot be retrieved.
			continue
		}
		for _, addr := range addrs {
			switch v := addr.(type) {
			case *net.IPNet:
				// Check if the IP address of this interface matches the target IP.
				if v.IP.Equal(targetIP) {
					return true
				}
			case *net.IPAddr:
				// Check if the IP address of this interface matches the target IP.
				if v.IP.Equal(targetIP) {
					return true
				}
			}
		}
	}
	// The target IP was not found on any local network interface.
	return false
}
