package cidr

import (
	"fmt"
	"net"

	"github.com/tidbcloud/infra-provider/pkg/resource"
)

func GetSubnetCIDR(cidr string, r resource.NetworkZonalResource, zone string, zones []string) (string, error) {
	ipPrefix, err := ValidateCIDR(cidr)
	if err != nil {
		return "", err
	}

	index := zoneToIndex(zone, zones)
	if index == -1 {
		return "", fmt.Errorf("zone %s is not available, available zones %v", zone, zones)
	}
	switch r {
	case resource.SubnetPodsNodes:
		if index > 3 {
			return "", fmt.Errorf("unspported zone: %v", zone)
		}
		// - pods-nodes-<az-a>: 10.x.64.0/18
		// - pods-nodes-<az-b>: 10.x.128.0/18
		// - pods-nodes-<az-c>: 10.x.192.0/18
		return newIPv4CIDR(ipPrefix, byte(64+64*index), 0, 18), nil
	case resource.SubnetPrivateNLB:
		if index > 6 {
			return "", fmt.Errorf("unspported zone: %v", zone)
		}
		//   - private-nlb-<az-a>: 10.x.0.0/23
		//   - private-nlb-<az-b>: 10.x.2.0/23
		//   - private-nlb-<az-c>: 10.x.4.0/23
		//   - private-nlb-<az-d>: 10.x.6.0/23  (reserved for some regions)
		//   - private-nlb-<az-e>: 10.x.8.0/23  (reserved for some regions)
		//   - private-nlb-<az-f>: 10.x.10.0/23 (reserved for some regions)
		return newIPv4CIDR(ipPrefix, byte(2*index), 0, 23), nil
	case resource.SubnetPublicNLB:
		if index > 3 {
			return "", fmt.Errorf("unspported zone: %v", zone)
		}
		//   - public-nlb-<az-a>:  10.x.12.0/23
		//   - public-nlb-<az-b>:  10.x.14.0/23
		//   - public-nlb-<az-c>:  10.x.16.0/23
		return newIPv4CIDR(ipPrefix, byte(12+2*index), 0, 23), nil
	case resource.SubnetControlPlane:
		if index > 3 {
			return "", fmt.Errorf("unspported zone: %v", zone)
		}
		// - control-plane-<az-a>:  10.x.31.0/26
		// - control-plane-<az-b>:  10.x.31.64/26
		// - control-plane-<az-c>:  10.x.31.128/26
		return newIPv4CIDR(ipPrefix, 31, byte(64*index), 26), nil
	case resource.SubnetO11y:
		if index > 3 {
			return "", fmt.Errorf("unspported zone: %v", zone)
		}
		// - o11y-<az-a>:  10.x.31.192/28
		// - o11y-<az-b>:  10.x.31.208/28
		// - o11y-<az-c>:  10.x.31.224/28
		return newIPv4CIDR(ipPrefix, 31, byte(192+16*index), 28), nil
	case resource.SubnetVPCEndpoint:
		if index > 3 {
			return "", fmt.Errorf("unspported zone: %v", zone)
		}
		// - vpc-endpoint-<az-a>:  10.x.30.0/24
		// - vpc-endpoint-<az-b>:  10.x.29.0/24
		// - vpc-endpoint-<az-c>:  10.x.28.0/24
		return newIPv4CIDR(ipPrefix, byte(30-index), 0, 24), nil
	case resource.SubnetNAT:
		if index > 3 {
			return "", fmt.Errorf("unspported zone: %v", zone)
		}
		// - nat-<az-a>:  10.x.27.0/24
		// - nat-<az-b>:  10.x.26.0/24
		// - nat-<az-c>:  10.x.25.0/24
		return newIPv4CIDR(ipPrefix, byte(27-index), 0, 24), nil
	}

	return "", fmt.Errorf("invalid subnet: %v", r)
}

func ValidateCIDR(cidr string) (net.IP, error) {
	_, ipNet, err := net.ParseCIDR(cidr)
	if err != nil {
		return nil, err
	}
	ipv4 := ipNet.IP.To4()
	if ipv4 == nil {
		return nil, fmt.Errorf("now only support ipv4 in shared vpc cidr")
	}
	if !ipv4.Mask(net.IPv4Mask(255, 0, 0, 0)).Equal(net.IPv4(10, 0, 0, 0)) {
		return nil, fmt.Errorf("cidr of shared vpc must be in 10.0.0.0/8")
	}
	ones, bits := ipNet.Mask.Size()
	if ones != 16 || bits != 32 {
		return nil, fmt.Errorf("cidr of shared vpc must be 10.x.0.0/16")
	}

	return ipv4, nil
}

func zoneToIndex(zone string, zones []string) int {
	for i, z := range zones {
		if z == zone {
			return i
		}
	}

	return -1
}

func newIPv4CIDR(ip net.IP, c, d byte, mask int) string {
	ipv4 := ip.To4()
	cidr := net.IPNet{
		// net.IP is a byte array
		// ipv4 is the last four bytes in the byte array
		// e.g.
		// 192.168.50.2
		// ip[0] = 192
		// ip[1] = 168
		// ip[2] = 50
		// ip[3] = 2
		IP: net.IPv4(ipv4[0], ipv4[1], c, d),
		// 32 means it's an ipv4 mask
		Mask: net.CIDRMask(mask, 32),
	}

	return cidr.String()
}
