package gke

import (
	"context"
	"fmt"
	"time"

	"github.com/crossplane-contrib/provider-gcp/apis/compute/v1alpha1"
	"github.com/crossplane-contrib/provider-gcp/apis/compute/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/tidbcloud/infra-provider/pkg/applier"
	"github.com/tidbcloud/infra-provider/pkg/applier/mutatefuncs"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	rsv1alpha1 "github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/internet/task"
)

// support 2,016 VMs if each uses 64 ports
// ⌊(2 NAT IP address) × (64,512 ports per address) / (64 ports per VM)⌋ = 2,016 VMs
// https://cloud.google.com/nat/docs/ports-and-addresses#port-reservation-examples
const defaultAddressCount = 2

type Router struct {
	*task.BaseTask
}

func NewRouter(ctx *task.Context) *Router {
	return &Router{BaseTask: &task.BaseTask{Context: ctx}}
}

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

func (t *Router) Create(ctx context.Context) deploy.Result {
	isLegacy := t.Internet.Labels[rsv1alpha1.IsLegacyKey] == "true"
	if len(t.VPC.Status.NodeSubnets) != 1 {
		return deploy.Panic(fmt.Errorf("node subnet not ready"))
	}

	subnet := &v1beta1.Subnetwork{}
	err := t.Local.Get(ctx, types.NamespacedName{
		Name: t.VPC.Status.NodeSubnets[0].Name,
	}, subnet)
	if err != nil {
		return deploy.Panic(err)
	}

	var addrObjs []*v1beta1.Address
	for i := 0; i < defaultAddressCount; i++ {
		name := t.newAddressName(i)
		addrObj := t.newAddress(name)
		if isLegacy {
			if _, err := controllerutil.CreateOrUpdate(ctx, t.Local, addrObj, func() error { return nil }); err != nil {
				return deploy.Error(err)
			}
		} else {
			if err := k8s.Apply(ctx, t.Local, addrObj); err != nil {
				return deploy.Error(err)
			}
		}
		addrObjs = append(addrObjs, addrObj)
	}
	ips := []string{}
	addressURLs := []string{}
	for _, addr := range addrObjs {
		if addr.Spec.ForProvider.Address == nil || len(addr.Status.AtProvider.SelfLink) == 0 {
			return deploy.Error(fmt.Errorf("address not ready name:%s", addr.Name))
		}
		ips = append(ips, *addr.Spec.ForProvider.Address)
		addressURLs = append(addressURLs, addr.Status.AtProvider.SelfLink)
	}
	t.Internet.Status.ExternalIPs = ips
	router, err := t.NewRouter(addressURLs, subnet)
	if err != nil {
		return deploy.Panic(err)
	}
	mutateFn := mutatefuncs.MutateRouter
	if isLegacy {
		mutateFn = func(_, _ *v1alpha1.Router) error {
			return nil
		}
	}

	if _, err := applier.NewApplier(t.Local, mutateFn).
		Expected(router).
		Apply(ctx); err != nil {
		return deploy.Error(err)
	}

	return deploy.Done()
}

func (t *Router) PostCreate(ctx context.Context) deploy.Result {
	var r v1alpha1.Router
	key := types.NamespacedName{Name: t.routerName()}
	if err := t.Local.Get(ctx, key, &r); err != nil {
		return deploy.Fatal(err)
	}

	condition := r.Status.ConditionedStatus.GetCondition(xpv1.TypeReady)
	if condition.Status != corev1.ConditionTrue {
		return deploy.Panic(fmt.Errorf("router %s not ready", r.Name)).WithRequeuAfter(time.Second * 30)
	}

	nats := []rsv1alpha1.RouterNAT{}

	for _, nat := range r.Spec.ForProvider.Nats {
		nats = append(nats, rsv1alpha1.RouterNAT{
			Name: *nat.Name,
			IPs:  nat.NatIps,
		})
	}

	t.Internet.Status.CloudRouters = []rsv1alpha1.CloudRouter{
		{
			Name:          r.Name,
			CloudRouterID: r.Status.AtProvider.ID,
			NATs:          nats,
		},
	}

	if err := t.InternetRW.UpdateStatus(ctx, t.Internet); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	return deploy.Done()
}

func (t *Router) Delete(ctx context.Context) deploy.Result {
	name := t.routerName()
	if err := t.Local.Delete(ctx, applier.NewClusterObject[*v1alpha1.Router](name)); err != nil {
		if !errors.IsNotFound(err) {
			return deploy.Error(err)
		}
	}

	for i := 0; i < defaultAddressCount; i++ {
		name2 := t.newAddressName(i)
		if err := t.Local.Delete(ctx, applier.NewClusterObject[*v1beta1.Address](name2)); err != nil {
			if !errors.IsNotFound(err) {
				return deploy.Error(err)
			}
		}
	}

	return deploy.Done()
}

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

func (t *Router) newAddressName(id int) string {
	if name := t.Internet.Annotations[rsv1alpha1.AnnotationKeyOwnCrossplaneNameNetwork]; name != "" {
		return fmt.Sprintf("addr-%d-%s", id, name)
	}
	return namer.UniqueIdentifier(t.Internet.ObjectMeta, namer.WithPrefix(fmt.Sprintf("addr-%d", id)))
}

func (t *Router) newAddress(name string) *v1beta1.Address {
	return &v1beta1.Address{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: v1beta1.AddressSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
			},
			ForProvider: v1beta1.AddressParameters{
				Description: pointer.String("ip address for nat"),
				Region:      t.Internet.Spec.Region,
			},
		},
	}
}

func (t *Router) routerName() string {
	if name := t.Internet.Annotations[rsv1alpha1.AnnotationKeyOwnCrossplaneNameRouter]; name != "" {
		return name
	}
	return namer.UniqueIdentifier(t.Internet.ObjectMeta, namer.WithPrefix("egress"))
}

func (t *Router) NewRouter(addressURLs []string, subnet *v1beta1.Subnetwork) (*v1alpha1.Router, error) {
	name := t.routerName()
	router := &v1alpha1.Router{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: v1alpha1.RouterSpec{
			ForProvider: v1alpha1.RouterParameters{
				Description: pointer.String("for pods in gke to access internet"),
				Region:      t.Internet.Spec.Region,
				NetworkRef: &xpv1.Reference{
					Name: t.VPC.Status.VPC.Name,
				},
				Nats: []*v1alpha1.RouterNat{
					{
						Name:                &name,
						NatIpAllocateOption: "MANUAL_ONLY",
						NatIps:              addressURLs,
						// TODO: minimize the ip ranges which are allowed to visit the internet
						SourceSubnetworkIpRangesToNat: "ALL_SUBNETWORKS_ALL_IP_RANGES",
					},
				},
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
			},
		},
	}
	if rsv1alpha1.IsOriginatedFromGardener(t.Internet) {
		if subnet.Status.AtProvider.SelfLink == "" {
			return nil, fmt.Errorf("subnetwork %s not ready", subnet.Name)
		}

		// Must specify the subnetworkof new-arch
		router.Spec.ForProvider.Nats = []*v1alpha1.RouterNat{{
			Name:                          &name,
			NatIpAllocateOption:           "MANUAL_ONLY",
			NatIps:                        addressURLs,
			SourceSubnetworkIpRangesToNat: "LIST_OF_SUBNETWORKS",
			Subnetworks: []*v1alpha1.RouterNatSubnetworkToNat{
				{
					// URL of subnetwork
					Name: pointer.String(subnet.Status.AtProvider.SelfLink),
				}}},
		}
	}
	return router, nil
}
