package gke

import (
	"context"
	"fmt"
	"time"

	gcpv1alpha1 "github.com/crossplane-contrib/provider-gcp/apis/compute/v1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"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/regional-server/api/v1alpha1"
	rsutils "github.com/tidbcloud/infra-provider/regional-server/pkg/common/utils"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpc/task"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/pointer"
)

type PSCFirewall struct {
	*task.BaseTask
}

func NewFirewall(ctx *task.Context) *PSCFirewall {
	return &PSCFirewall{&task.BaseTask{Context: ctx}}
}

func (t *PSCFirewall) Name() string {
	return "Firewall"
}

func (t *PSCFirewall) Create(ctx context.Context) deploy.Result {
	if t.IsHibernated() {
		return deploy.Done()
	}
	cidrDispatcher, err := controllers.GetGKECIDRDispatcher(t.VPC, t.Config)
	if err != nil {
		return deploy.Panic(err)
	}

	firewall := &gcpv1alpha1.Firewall{
		ObjectMeta: metav1.ObjectMeta{Name: t.firewallName()},
		Spec: gcpv1alpha1.FirewallSpec{
			ForProvider: gcpv1alpha1.FirewallParameters{
				Direction: pointer.String("INGRESS"),
				Allowed: []*gcpv1alpha1.FirewallAllowed{
					{
						IPProtocol: "tcp",
					},
				},
				SourceRanges: cidrDispatcher.PSCNATIPv4CIDRBlocks(),
				NetworkRef: &xpv1.Reference{
					Name: t.GetNetworkName(),
				},
				// Not specify targetTags, so all instances in the network will be applied
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
			},
		},
	}
	applier := applier.NewApplier(t.Local, mutatefuncs.MutateFirewall)
	if _, err := applier.Expected(firewall).Apply(ctx); err != nil {
		return deploy.Error(err)
	}
	return deploy.Done()
}

func (t *PSCFirewall) PostCreate(ctx context.Context) deploy.Result {
	if t.IsHibernated() {
		return deploy.Done()
	}
	firewall := &gcpv1alpha1.Firewall{}
	key := types.NamespacedName{
		Name: t.firewallName(),
	}
	err := t.Local.Get(ctx, key, firewall)
	if err != nil {
		return deploy.Error(err)
	}

	ready, synced := rsutils.GetConditions(firewall.Status.Conditions)
	if !ready || !synced {
		return deploy.Panic(fmt.Errorf("PSC firewall not ready or not synced"))
	}

	t.VPC.Status.PSCFirewall = v1alpha1.Firewall{
		Name:         firewall.Name,
		FirewallID:   firewall.Status.AtProvider.ID,
		SourceRanges: firewall.Spec.ForProvider.SourceRanges,
	}
	if err := t.VPCRW.UpdateStatus(ctx, t.VPC); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}

	return deploy.Done()
}

func (t *PSCFirewall) firewallName() string {
	if name := t.VPC.Annotations[v1alpha1.AnnotationKeyOwnCrossplaneNamePSCFirewall]; name != "" {
		return name
	}
	// firewall name require start with a letter
	return fmt.Sprintf("psc-ingress-%s", t.VPC.GetName())
}

func (t *PSCFirewall) Delete(ctx context.Context) deploy.Result {
	firewall := &gcpv1alpha1.Firewall{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.firewallName(),
		},
	}
	return deploy.ErrorOrDone(k8s.DeleteObject(ctx, t.Local, firewall))
}

func (t *PSCFirewall) PostDelete(ctx context.Context) deploy.Result {
	return deploy.ErrorOrDone(k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{Name: t.firewallName()}, &gcpv1alpha1.Firewall{}))
}
