package utils

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

func IsValidCIDR(cidr string) bool {
	_, _, err := net.ParseCIDR(cidr)
	return err == nil
}

// ValidSecondaryCidr validates whether newSecondaryCidr can be added as a secondary IPv4 CIDR
// to a VPC with primaryCidr and existingSecondaryCidrs, according to AWS VPC restrictions.
// See: https://docs.aws.amazon.com/vpc/latest/userguide/vpc-cidr-blocks.html#add-cidr-block-restrictions
func ValidSecondaryCidr(primaryCidr string, existingSecondaryCidrs []string, newSecondaryCidr string) (bool, error) {
	_, primaryNet, err := net.ParseCIDR(primaryCidr)
	if err != nil {
		return false, fmt.Errorf("invalid primary CIDR: %w", err)
	}
	_, newNet, err := net.ParseCIDR(newSecondaryCidr)
	if err != nil {
		return false, fmt.Errorf("invalid new secondary CIDR: %w", err)
	}
	// Check IPv4
	if primaryNet.IP.To4() == nil || newNet.IP.To4() == nil {
		return false, fmt.Errorf("only IPv4 CIDRs are supported")
	}
	if !isPrivate(primaryNet.IP) {
		return false, fmt.Errorf("primary CIDR must be a private CIDR")
	}
	if !isPrivate(newNet.IP) {
		return false, fmt.Errorf("secondary CIDR must be a private CIDR")
	}
	// Prefix length: /16 - /28
	ones, bits := newNet.Mask.Size()
	if bits != 32 || ones < 16 || ones > 28 {
		return false, fmt.Errorf("secondary CIDR block must have a prefix length between /16 and /28")
	}

	// AWS restriction: Secondary CIDR must be from the same address family (private/public) as the primary,
	// For primary in 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16, secondary must also be in the same range,
	if !samePrivateRange(primaryNet, newNet) {
		return false, fmt.Errorf(
			"the CIDR is restricted. Use a CIDR from the same private address range as the current VPC CIDR, or use a publicly-routable CIDR. " +
				"For additional restrictions, see https://docs.aws.amazon.com/vpc/latest/userguide/vpc-cidr-blocks.html",
		)
	}

	// Overlap with primary
	if cidrOverlap(primaryNet, newNet) {
		return false, fmt.Errorf("new secondary CIDR overlaps with the primary CIDR")
	}

	existingSecondaryIPs := make([]net.IP, 0, len(existingSecondaryCidrs))
	// Overlap with existing
	for _, sec := range existingSecondaryCidrs {
		_, secNet, err := net.ParseCIDR(sec)
		if err != nil {
			return false, fmt.Errorf("invalid existing secondary CIDR: %w", err)
		}
		if cidrOverlap(secNet, newNet) {
			return false, fmt.Errorf("new secondary CIDR overlaps with existing secondary CIDR %q", sec)
		}
		existingSecondaryIPs = append(existingSecondaryIPs, secNet.IP)
	}

	// Reserved/restricted ranges
	if isRestrictedAWSRange(primaryNet.IP, existingSecondaryIPs, newNet.IP) {
		return false, fmt.Errorf("secondary CIDR is in a reserved or restricted range")
	}

	return true, nil
}

func isPrivate(ip net.IP) bool {
	privateRanges := []string{
		"10.0.0.0/8",
		"172.16.0.0/12",
		"192.168.0.0/16",
	}
	for _, cidr := range privateRanges {
		_, n, _ := net.ParseCIDR(cidr)
		if n.Contains(ip) {
			return true
		}
	}
	return false
}

func samePrivateRange(a, b *net.IPNet) bool {
	privateRanges := []string{
		"10.0.0.0/8",
		"172.16.0.0/12",
		"192.168.0.0/16",
	}
	for _, cidr := range privateRanges {
		_, n, _ := net.ParseCIDR(cidr)
		if n.Contains(a.IP) && n.Contains(b.IP) {
			return true
		}
	}
	return false
}

func isRestrictedAWSRange(primaryIP net.IP, existingSecondaryCidrs []net.IP, ip net.IP) bool {
	_, n198_19, _ := net.ParseCIDR("198.19.0.0/16")

	// If the primaryIP belongs to 10.0.0.0/8, then If any of the CIDR blocks associated with the VPC are from the 10.0.0.0/15 range (10.0.0.0 to 10.1.255.255),
	// you cannot add a CIDR block from the 10.0.0.0/16 range (10.0.0.0 to 10.0.255.255), CIDR blocks from the 198.19.0.0/16 range are restricted for secondary
	_, n10_0, _ := net.ParseCIDR("10.0.0.0/8")
	_, n10_0_15, _ := net.ParseCIDR("10.0.0.0/15")
	_, n10_0_16, _ := net.ParseCIDR("10.0.0.0/16")
	if n10_0.Contains(primaryIP) {
		for _, sec := range existingSecondaryCidrs {
			if n10_0_15.Contains(sec) && n10_0_16.Contains(ip) {
				return true
			}
		}
	}

	// If the primaryIP belongs to 172.16.0.0/12, then 172.31.0.0/16 and 198.19.0.0/16 are restricted for secondary
	_, n172, _ := net.ParseCIDR("172.16.0.0/12")
	_, n172_31, _ := net.ParseCIDR("172.31.0.0/16")
	if n172.Contains(primaryIP) {
		if n172_31.Contains(ip) || n198_19.Contains(ip) {
			return true
		}
	}

	// if the primaryIP belongs to 192.168.0.0/16, then 198.19.0.0/16 is restricted for secondary
	_, n192, _ := net.ParseCIDR("192.168.0.0/16")
	if n192.Contains(primaryIP) {
		if n198_19.Contains(ip) {
			return true
		}
	}

	return false
}

func cidrOverlap(a, b *net.IPNet) bool {
	return a.Contains(b.IP) || b.Contains(a.IP)
}

// SanitizeCidr sanitizes the CIDR block by replacing dots and slashes with hyphens
// This ensures the resulting name is valid for Kubernetes resources
// For example, "10.0.0.0/16" becomes "10-0-0-0-16"
func SanitizeCidr(cidr string) string {
	return strings.NewReplacer(".", "-", "/", "-").Replace(strings.TrimSpace(cidr))
}

// RecoverSanitizedCidr recovers the original CIDR block from the sanitized name
// For example, "10-0-0-0-16" becomes "10.0.0.0/16"
func RecoverSanitizedCidr(sanitizedCidr string) string {
	parts := strings.Split(strings.TrimSpace(sanitizedCidr), "-")
	if len(parts) < 2 {
		return sanitizedCidr // Not a valid sanitized CIDR
	}
	ip := strings.Join(parts[:len(parts)-1], ".")
	return ip + "/" + parts[len(parts)-1]
}

// CidrContains checks if childCIDR is fully contained within parentCIDR
// It returns true if childCIDR is fully contained within parentCIDR, false otherwise
// It returns an error if the CIDRs are invalid
func CidrContains(parentCIDR, childCIDR string) (bool, error) {
	_, parentNet, err := net.ParseCIDR(parentCIDR)
	if err != nil {
		return false, err
	}
	childIP, childNet, err := net.ParseCIDR(childCIDR)
	if err != nil {
		return false, err
	}

	// Check if the first IP of child is in parent
	if !parentNet.Contains(childIP) {
		return false, nil
	}

	// Get the last IP of childNet (broadcast address)
	childLastIP := make(net.IP, len(childNet.IP))
	copy(childLastIP, childNet.IP)
	for i := range childNet.Mask {
		childLastIP[i] |= ^childNet.Mask[i]
	}

	// Check if the last IP of child is in parent
	if !parentNet.Contains(childLastIP) {
		return false, nil
	}

	return true, nil
}
