package networkpolicy

import (
	"context"
	"fmt"

	corev1 "k8s.io/api/core/v1"
	netv1 "k8s.io/api/networking/v1"
	"k8s.io/apimachinery/pkg/util/intstr"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/shared-provider/pkg/types"
	"github.com/tidbcloud/shared-provider/pkg/utils/labels"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

const (
	cidrSuffixForIPV4 = "/32"
	awsMetaServerCIDR = "169.254.169.254/32"
)

var (
	// nolint: gomnd // it's default port for dns
	dnsPort = intstr.FromInt(53)
	// nolint: gomnd // it's default port for tidb
	tidbPort = intstr.FromInt(4000)
	// tidbHealthCheckPort = intstr.FromInt(10080)

	tcpProtocol = corev1.ProtocolTCP
	udpProtocol = corev1.ProtocolUDP
)

func ipToCIDR(ip string) string {
	return ip + cidrSuffixForIPV4
}

func (r *Reconciler) sync(ctx context.Context, ns, name string) error {
	var policy *netv1.NetworkPolicy
	switch name {
	case AllowToAPIServer:
		ep := &corev1.Endpoints{}
		if err := r.Get(ctx, client.ObjectKey{
			Name:      types.KubernetesEndpointName,
			Namespace: corev1.NamespaceDefault,
		}, ep); err != nil {
			return fmt.Errorf("can't get kubernetes endpoints: %w", err)
		}

		policy = NewAllowToAPIServer(ns, KubernetesEndpoint2CIDR(ep))
	case DefaultDenyName:
		policy = NewDefaultDeny(ns)
	case AllowDNSAccessName:
		policy = NewAllowDNSAccess(ns)
	case AllowAllInNamespace:
		policy = NewAllowAllInNamespce(ns)
	case AllowMonitorToThanos:
		ep := &corev1.Endpoints{}
		if err := r.Get(ctx, client.ObjectKey{
			Name:      types.KubernetesEndpointName,
			Namespace: corev1.NamespaceDefault,
		}, ep); err != nil {
			return fmt.Errorf("can't get kubernetes endpoints: %w", err)
		}
		policy = NewAllowMonitorToThanos(ns, appendAWSMetaServerCIDR(KubernetesEndpoint2CIDR(ep)))
	case AllowAccessAWSService:
		ep := &corev1.Endpoints{}
		if err := r.Get(ctx, client.ObjectKey{
			Name:      types.KubernetesEndpointName,
			Namespace: corev1.NamespaceDefault,
		}, ep); err != nil {
			return fmt.Errorf("can't get kubernetes endpoints: %w", err)
		}
		policy = NewAllowAccessAWSService(ns, appendAWSMetaServerCIDR(KubernetesEndpoint2CIDR(ep)))
	case AllowFromManager:
		policy = NewAllowFromManager(ns)
	case AllowFromDBaaSProxy:
		policy = NewAllowFromDBaaSProxy(ns)
	case AllowFromSliReporter:
		policy = NewAllowFromSliReporter(ns)
	case AllowFromLoadBalancer:
		policy = NewAllowFromLoadBalancer(ns, r.LoadBalancerCIDRs...)

	// delete the legacy policy
	case "default":
		// Keep now, there are many old version manager(e2e) will create it since no isolate.
		if false {
			key := &netv1.NetworkPolicy{}
			key.Namespace = ns
			key.Name = name
			err := r.Delete(ctx, key)
			if client.IgnoreNotFound(err) != nil {
				return errors.AddStack(err)
			}
		}
	}

	if policy != nil {
		policy.Labels = r.addCommonPolicyLabels(policy.Labels)
		expect := policy.DeepCopy()
		_, err := controllerutil.CreateOrUpdate(
			ctx,
			r.Client,
			policy,
			func() error { policy.Spec = expect.Spec; return nil },
		)
		if err != nil {
			return errors.Annotatef(err, "failed to create or update policy %s/%s", ns, name)
		}
	}

	return nil
}

func (r *Reconciler) addCommonPolicyLabels(ls map[string]string) map[string]string {
	if ls == nil {
		ls = map[string]string{}
	}
	ls[labels.LabelKeyKubernetesCluster] = r.KubeClusterName

	return ls
}

func KubernetesEndpoint2CIDR(ep *corev1.Endpoints) (cidrs []string) {
	for _, subset := range ep.Subsets {
		for _, addr := range subset.Addresses {
			cidrs = append(cidrs, ipToCIDR(addr.IP))
		}

		for _, addr := range subset.NotReadyAddresses {
			cidrs = append(cidrs, ipToCIDR(addr.IP))
		}
	}

	return
}

func appendAWSMetaServerCIDR(cidrs []string) []string {
	return append(cidrs, awsMetaServerCIDR)
}
