package aws

import (
	"context"
	"strings"

	"github.com/aws/aws-sdk-go-v2/service/ec2"
	ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpcpeering/task"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type VPCPeeringAccept struct {
	*task.BaseTask
}

func NewVPCPeeringAccept(c *task.Context) *VPCPeeringAccept {
	return &VPCPeeringAccept{
		BaseTask: &task.BaseTask{
			Context: c,
		},
	}
}

func (p *VPCPeeringAccept) Name() string {
	return "VPCPeeringAccept"
}

func (p *VPCPeeringAccept) Create(ctx context.Context) deploy.Result {
	connectionID := p.VPCPeering.Spec.VPCPeeringConnectionID
	approved := p.VPCPeering.GetAnnotations()[v1alpha1.AnnotationKeyVPCPeeringApproved] == "true"
	if !approved {
		_, err := p.EC2Client.AcceptVpcPeeringConnection(ctx, &ec2.AcceptVpcPeeringConnectionInput{
			VpcPeeringConnectionId: ptr.To(connectionID),
		})
		if err != nil {
			return deploy.Panic(err).WithRequeuAfter(defaultWaitDuration)
		}
		p.VPCPeering.Annotations[v1alpha1.AnnotationKeyVPCPeeringApproved] = "true"
		return deploy.Panic(nil).WithRequeuAfter(quickRetryDuration)
	}

	allowed := p.VPCPeering.GetAnnotations()[v1alpha1.AnnotationKeyVPCPeeringAllowRemoteDNS] == "true"
	if !allowed {
		_, err := p.EC2Client.ModifyVpcPeeringConnectionOptions(ctx, &ec2.ModifyVpcPeeringConnectionOptionsInput{
			VpcPeeringConnectionId: ptr.To(connectionID),
			AccepterPeeringConnectionOptions: &ec2types.PeeringConnectionOptionsRequest{
				AllowDnsResolutionFromRemoteVpc: ptr.To(true),
			},
		})
		if err != nil {
			return deploy.Panic(err).WithRequeuAfter(defaultWaitDuration)
		}
		p.VPCPeering.Annotations[v1alpha1.AnnotationKeyVPCPeeringAllowRemoteDNS] = "true"
	}
	routeTableReady := p.Internet.GetAnnotations()[v1alpha1.AnnotationKeyRouteTableEnsuredAcceptorPeeringPrefix+p.VPCPeering.Name] == "true"
	if !routeTableReady {
		requesterCIDR := p.VPCPeering.Spec.Target.AWS.PeerCIDR
		err := addRoute(ctx, p.EC2Client, p.Logger, []string{p.VPC.Status.VPC.VPCID}, requesterCIDR, ptr.To(connectionID))
		if err != nil {
			return deploy.Panic(err).WithRequeuAfter(defaultWaitDuration)
		}
		// set the annotation of the internet to true to indicate the route table is ensured.
		if p.Internet.Annotations == nil {
			p.Internet.Annotations = make(map[string]string)
		}
		p.Internet.Annotations[v1alpha1.AnnotationKeyRouteTableEnsuredAcceptorPeeringPrefix+p.VPCPeering.Name] = "true"
		if err := p.Local.Update(ctx, p.Internet); err != nil {
			return deploy.Panic(err)
		}
	}
	p.VPCPeering.Status.Phase = v1alpha1.VPCPeeringPhaseActive
	p.VPCPeering.Status.Reason = "VPCPeeringConnection is already accepted"
	// always requeue
	return deploy.Done().WithRequeuAfter(LongWaitDuration)
}

func (p *VPCPeeringAccept) Delete(ctx context.Context) deploy.Result {
	requesterCIDR := p.VPCPeering.Spec.Target.AWS.PeerCIDR
	connectionID := p.VPCPeering.Spec.VPCPeeringConnectionID
	vpc := &v1alpha1.VPC{}
	if err := p.Local.Get(ctx, client.ObjectKey{
		Namespace: p.VPCPeering.Spec.SourceVPCRef.Namespace,
		Name:      p.VPCPeering.Spec.SourceVPCRef.Name,
	}, vpc); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultWaitDuration)
	}
	err := deleteRoute(ctx, p.EC2Client, p.Logger, []string{vpc.Status.VPC.VPCID}, requesterCIDR, ptr.To(connectionID))
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultWaitDuration)
	}
	_, err = p.EC2Client.DeleteVpcPeeringConnection(ctx, &ec2.DeleteVpcPeeringConnectionInput{
		VpcPeeringConnectionId: ptr.To(connectionID),
	})
	// if the vpc peering connection is not found, we can ignore the error
	if err != nil && !strings.Contains(err.Error(), "InvalidVpcPeeringConnectionID.NotFound") {
		return deploy.Panic(err).WithRequeuAfter(defaultWaitDuration)
	}
	return deploy.Done()
}
