package task

import (
	"context"
	"encoding/binary"
	"fmt"
	"math"
	"net"
	"sort"
	"time"

	"github.com/crossplane-contrib/provider-aws/apis/eks/v1beta1"
	gkev1beta2 "github.com/crossplane-contrib/provider-gcp/apis/container/v1beta2"
	"github.com/go-logr/logr"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"

	"github.com/tidbcloud/eks-provider/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/pkg/account"
	"github.com/tidbcloud/infra-provider/pkg/config"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/gitops"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

const (
	Finalizer = "network.cloud.pingcap.com/finalizer"

	AdminNamespace = "tidb-admin"

	// ObservabilityNamespace is the namespace where observability related addons are deployed.
	ObservabilityNamespace = "monitoring"
	CertManagerNamespace   = "cert-manager"

	// CIDR
	GKEWorkerMaxPodsPerNode = int64(8)
	GKEAdminMaxPodsPerNode  = int64(32)
)

type Context struct {
	// Transient during a deployment
	Namer   *namer.Namer
	Cluster ClusterIdentifier
	// Remote refers to dataplane's apiserver.
	Remote  k8s.ClientGetter // nolint:structcheck
	Account *cloudv1.Account // reference Account which is set by the task `Account`

	// Injected from the caller
	Logger logr.Logger
	// Local refers to infra-api's apiserver.
	Local    client.Client
	Network  *cloudv1.Network
	Provider gitops.Provider
	Config   config.RootConfig

	AcctInfoGetter account.LegacyInfoGetter

	// eks specified
	EKSNetworkSpec *v1alpha1.EKSNetworkSpec

	// gke specified
	GKENetworkSpec    *v1alpha1.GKENetworkSpec
	GKECIDRDispatcher *gkeCIDRDispatcher

	IsMigrate bool

	zones []string

	// BeginAt denotes the starting time of the task.
	BeginAt *time.Time
}

func (c *Context) Default() (*Context, error) {
	c.Remote = &k8s.EmptyGetter{}
	c.Namer = namer.NewNamer().
		LegacyWithNetworkName(c.Network.Name).
		LegacyWithNetworkNamespace(c.Network.Namespace)

	providerType := c.Provider.GetProviderType()
	switch providerType {
	case consts.ProviderTypeEKS:
		networkSpec, err := v1alpha1.EKSNetworkSpecForNetwork(c.Network)
		if err != nil {
			return nil, fmt.Errorf("unable to get network spec: %v", err)
		}
		c.EKSNetworkSpec = networkSpec

	case consts.ProviderTypeGKE:
		networkSpec, err := v1alpha1.GKENetworkSpecForNetwork(c.Network)
		if err != nil {
			return nil, fmt.Errorf("unable to get network spec: %v", err)
		}
		c.GKENetworkSpec = networkSpec

		c.GKECIDRDispatcher, err = newGKECIDRDispatcher(c.Network.Spec.CIDR)
		if err != nil {
			return nil, err
		}
		err = c.GKECIDRDispatcher.Allocate()
		if err != nil {
			return nil, err
		}

	default:
		return nil, fmt.Errorf("unknown provider type: %s", providerType)
	}

	zones, err := c.Config.FindRegionZones(c.Network.Spec.Region)
	if err != nil {
		return nil, fmt.Errorf("can't get zones of %s", c.Network.Spec.Region)
	}
	if len(zones) != 3 {
		return nil, fmt.Errorf("get available zones %v, the length of zones must be 3", zones)
	}
	sort.Strings(zones)
	c.zones = zones

	return c, nil
}

func (c *Context) Zones() []string {
	if c.zones == nil {
		panic("unreachable")
	}
	return c.zones
}

func (c *Context) GetTenantID() string {
	return util.GetLabel(c.Network, cloudv1.TenantTagKey)
}

func (c *Context) GetProjectID() string {
	return util.GetLabel(c.Network, cloudv1.ProjectTagKey)
}

var _ deploy.Task = &BaseTask{}

type BaseTask struct {
	*Context
}

func (t *BaseTask) Name() string {
	return ""
}

func (t *BaseTask) Category() deploy.TaskCategory {
	return deploy.NetworkTask
}

func (t *BaseTask) Create(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *BaseTask) PostCreate(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *BaseTask) Delete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *BaseTask) PostDelete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

// MetricsStartTime will return the `BeginAt` (nil by default). For
// Task whose metrics need to be exported, the `BeginAt` should be
// initialized when the `Create` is being called.
func (t *BaseTask) MetricsStartTime() *time.Time {
	return t.Context.BeginAt
}

type ClusterIdentifier interface {
	GetNamespace() string
	GetName() string
	GetUID() types.UID
}

type eksCluster struct {
	cluster *v1beta1.Cluster
}

func NewEKSClusterIdentifier(cluster *v1beta1.Cluster) *eksCluster {
	return &eksCluster{cluster: cluster}
}

func (c eksCluster) GetNamespace() string {
	return c.cluster.GetNamespace()
}

func (c eksCluster) GetUID() types.UID {
	return c.cluster.GetUID()
}

func (c eksCluster) GetName() string {
	return c.cluster.Name
}

type gkeCluster struct {
	cluster *gkev1beta2.Cluster
}

func NewGKEClusterIdentifier(cluster *gkev1beta2.Cluster) *gkeCluster {
	return &gkeCluster{cluster: cluster}
}

func (c gkeCluster) GetNamespace() string {
	return c.cluster.GetNamespace()
}

func (c gkeCluster) GetUID() types.UID {
	return c.cluster.GetUID()
}

func (c gkeCluster) GetName() string {
	return c.cluster.Name
}

// gkeCIDRDispatcher dispatcher CIDR with the last 12 bits(prefix /20), including
// - Pod IPv4 CIDR block(0, /21)
// - Node IPv4 CIDR block(100, /23)
// - Service IPv4 CIDR block(110, /23)
// - Master IPv4 CIDR block(11100000, /28)
type gkeCIDRDispatcher struct {
	prefix *net.IPNet

	podNet, nodeNet, serviceNet, masterNet *net.IPNet

	// CIDR for PSC NAT
	pscNATNet *net.IPNet
}

func newGKECIDRDispatcher(cidr string) (*gkeCIDRDispatcher, error) {
	_, prefixNet, err := net.ParseCIDR(cidr)
	if err != nil {
		return nil, err
	}
	// gke requires that the pod network cidr maximum size is /21, so the entire cidr prefix size must be less than or equal to /20
	if size, _ := prefixNet.Mask.Size(); size > 20 {
		return nil, fmt.Errorf("too large CIDR prefix, size: %d", size)
	}
	if len(prefixNet.IP.To4()) != 4 {
		return nil, fmt.Errorf("not an IPv4 address, %s", prefixNet.IP.To4().String())
	}

	return &gkeCIDRDispatcher{
		prefix: prefixNet,
	}, nil
}

func SplitSubnet(ipNet *net.IPNet, count int) ([]*net.IPNet, error) {
	// get start ip and mask size
	ip := ipNet.IP.To4()
	maskSize, _ := ipNet.Mask.Size()

	// caculate new mask size
	newMaskSize := maskSize + int(math.Log2(float64(count)))

	// check if new mask size is valid
	if newMaskSize > 32 {
		return nil, fmt.Errorf("can not split subnets %d", count)
	}

	// create subnets list
	subnets := make([]*net.IPNet, count)

	// caculate subnets size
	subnetSize := int(math.Pow(2, float64(32-newMaskSize)))

	// allocate subnets
	for i := 0; i < count; i++ {
		start := make(net.IP, len(ip))
		copy(start, ip)
		startInt := IPToInt(start)
		startInt += uint32(i) * uint32(subnetSize)

		subnet := &net.IPNet{
			IP:   IntToIP(startInt),
			Mask: net.CIDRMask(newMaskSize, 32),
		}

		subnets[i] = subnet
	}

	return subnets, nil
}

func IPToInt(ip net.IP) uint32 {
	return binary.BigEndian.Uint32(ip.To4())
}

func IntToIP(i uint32) net.IP {
	ip := make(net.IP, 4)
	binary.BigEndian.PutUint32(ip, i)
	return ip
}

func (c *gkeCIDRDispatcher) Allocate() error {
	// split subnets to 2 parts
	nets, err := SplitSubnet(c.prefix, 2)
	if err != nil {
		return err
	}

	// part 0 for pod and part 1 for node and service
	c.podNet = nets[0]

	nets02, err := SplitSubnet(nets[1], 4)
	if err != nil {
		return err
	}

	c.nodeNet = nets02[0]
	c.serviceNet = nets02[1]

	ones, _ := nets02[2].Mask.Size()
	// master mask is /28
	nets03, err := SplitSubnet(nets02[2], int(math.Pow(2, float64(28-ones))))
	if err != nil {
		return err
	}

	c.masterNet = nets03[0]

	// PSC NAT
	pscNATNet := deepCopy(nets03[0])
	pscNATNet.IP.To4()[3] |= 0b10000000
	pscNATNet.Mask = net.CIDRMask(25, 32)
	c.pscNATNet = pscNATNet
	return nil
}

// Default will only used for migration cluster.
func (c *gkeCIDRDispatcher) Default() {
	// Pod
	podNet := c.prefixDeepCopy()
	podNet.Mask = net.CIDRMask(21, 32)
	c.podNet = podNet

	// Node
	nodeNet := c.prefixDeepCopy()
	nodeNet.IP.To4()[2] |= 0b00001000
	nodeNet.Mask = net.CIDRMask(23, 32)
	c.nodeNet = nodeNet

	// Service
	serviceNet := c.prefixDeepCopy()
	serviceNet.IP.To4()[2] |= 0b00001100
	serviceNet.Mask = net.CIDRMask(23, 32)
	c.serviceNet = serviceNet

	// Master
	masterNet := c.prefixDeepCopy()
	masterNet.IP.To4()[2] |= 0b00001110
	masterNet.Mask = net.CIDRMask(28, 32)
	c.masterNet = masterNet

	// PSC NAT
	pscNATNet := c.prefixDeepCopy()
	pscNATNet.IP.To4()[2] |= 0b00001110
	pscNATNet.IP.To4()[3] |= 0b10000000
	pscNATNet.Mask = net.CIDRMask(25, 32)
	c.pscNATNet = pscNATNet
}

func (c *gkeCIDRDispatcher) GetPrefixNetDeepCopy() *net.IPNet {
	return c.prefixDeepCopy()
}

func (c *gkeCIDRDispatcher) PodIPv4CIDRBlock() string {
	return c.podNet.String()
}

func (c *gkeCIDRDispatcher) NodeIPv4CIDRBlock() string {
	return c.nodeNet.String()
}

func (c *gkeCIDRDispatcher) ServiceIPv4CIDRBlock() string {
	return c.serviceNet.String()
}

func (c *gkeCIDRDispatcher) MasterIPv4CIDRBlock() string {
	return c.masterNet.String()
}

func (c *gkeCIDRDispatcher) PSCNATIPv4CIDRBlock() string {
	return c.pscNATNet.String()
}

func (c *gkeCIDRDispatcher) prefixDeepCopy() *net.IPNet {
	cidr := new(net.IPNet)

	cidr.IP = make([]byte, len(c.prefix.IP))
	copy(cidr.IP, c.prefix.IP)

	cidr.Mask = make([]byte, len(c.prefix.Mask))
	copy(cidr.Mask, c.prefix.Mask)

	return cidr
}

// The 20th bit is reserved when the network cidr prefix size is 19
// Set it to 1 to avoid CIDR overlap
func (c *gkeCIDRDispatcher) SetPrefixPlaceholder() error {
	if ones, _ := c.prefix.Mask.Size(); ones > 19 {
		return fmt.Errorf("prefix size is %d, bigger then 19, no placeholder left", ones)
	}
	c.prefix.IP.To4()[2] |= 0b00010000
	c.Default()
	return nil
}

func deepCopy(old *net.IPNet) *net.IPNet {
	cidr := new(net.IPNet)

	cidr.IP = make([]byte, len(old.IP))
	copy(cidr.IP, old.IP)

	cidr.Mask = make([]byte, len(old.Mask))
	copy(cidr.Mask, old.Mask)

	return cidr
}
