package ack

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/alibabacloud-go/tea/tea"
	vpc "github.com/alibabacloud-go/vpc-20160428/v6/client"
	alieipv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/eip/v1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/util/retry"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/cloud/aliyun"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/internet/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
)

const (
	eipPaymentType   = "PayAsYouGo"
	eipChargeType    = "PayByTraffic"
	eipBandwidthMbps = "200"

	annotationCommonBandwidthAttachedKey   = "tidbcloud.com/common-bandwidth-attached"
	annotationCommonBandwidthAttachedValue = "true"
)

type ipAddressTask struct {
	*task.BaseTask
	aliyunCli aliyun.Client

	// addCommonBandwidthPackageIpFn and removeCommonBandwidthPackageIpFn are function pointers
	// that enable dependency injection for unit testing.
	// In tests, they can be replaced with mock functions to avoid real cloud API calls and
	// to verify call behavior (e.g., call count, error handling).
	addCommonBandwidthPackageIpFn    func(*alieipv1alpha1.EIPAddress) (bool, error)
	removeCommonBandwidthPackageIpFn func(*alieipv1alpha1.EIPAddress) error
}

func NewIPAddress(ctx *task.Context, aliyunCli aliyun.Client) deploy.Task {
	t := &ipAddressTask{
		BaseTask:  &task.BaseTask{Context: ctx},
		aliyunCli: aliyunCli,
	}
	t.addCommonBandwidthPackageIpFn = t.addCommonBandwidthPackageIp
	t.removeCommonBandwidthPackageIpFn = t.removeCommonBandwidthPackageIp
	return t
}

func (n *ipAddressTask) Name() string {
	return "IPAddress"
}

func (n *ipAddressTask) Create(ctx context.Context) deploy.Result {
	tags := controllers.ConvertToCloudTags(n.Internet.Labels, consts.ProviderTypeACK)
	tags = util.MergeMaps(tags, n.Config.AdditionalCloudTags)
	return deploy.PanicOrDone(n.createIP(ctx, n.getIPName(), tags))
}

func (n *ipAddressTask) createIP(ctx context.Context, name string, tags map[string]string) error {
	labels := util.MergeMaps(n.Internet.Labels, map[string]string{v1alpha1.InternetNameKey: n.Internet.Name})
	ip := &alieipv1alpha1.EIPAddress{
		ObjectMeta: metav1.ObjectMeta{
			Name:   name,
			Labels: labels,
		},
		Spec: alieipv1alpha1.EIPAddressSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: n.ProviderConfigName()},
				ManagementPolicies:      controllers.GetResourceManagementPolicies(),
			},
			ForProvider: alieipv1alpha1.EIPAddressParameters{
				AddressName:        ptr.To(name),
				Region:             ptr.To(n.Internet.Spec.Region),
				PaymentType:        ptr.To(eipPaymentType),
				InternetChargeType: ptr.To(eipChargeType),
				Bandwidth:          ptr.To(eipBandwidthMbps),
				Tags:               util.ToMapPointer(tags),
			},
		},
	}
	return k8s.Apply(ctx, n.Local, ip)
}

func (n *ipAddressTask) getIPName() string {
	return n.InternetName() + "-nat"
}

func (n *ipAddressTask) PostCreate(ctx context.Context) deploy.Result {
	ipRes := &alieipv1alpha1.EIPAddress{
		ObjectMeta: metav1.ObjectMeta{
			Name: n.getIPName(),
		},
	}
	if err := n.Local.Get(ctx, client.ObjectKeyFromObject(ipRes), ipRes); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	if !controllers.IsResourceReady(ipRes.Status.ResourceStatus) {
		return deploy.Error(fmt.Errorf("eip %s is not ready", ipRes.Name)).WithRequeuAfter(time.Second * 30)
	}

	if n.shouldAttachCommonBandwidthPackage(ipRes) {
		attached, err := n.addCommonBandwidthPackageIpFn(ipRes)
		if err != nil {
			if isAlreadyInBandwidthPackageError(err) {
				attached = true
			} else {
				return deploy.Panic(err)
			}
		}
		if attached {
			if err = n.markCommonBandwidthAttached(ctx, ipRes); err != nil {
				return deploy.Panic(err)
			}
		}
	}

	records := []v1alpha1.IPAddress{
		{
			AllocationID: *ipRes.Status.AtProvider.AllocationID,
			Name:         ipRes.Name,
			IP:           *ipRes.Status.AtProvider.IPAddress,
		},
	}
	n.Internet.Status.IPAddresses = records
	if err := n.InternetRW.UpdateStatus(ctx, n.Internet); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	return deploy.Done()
}

func (n *ipAddressTask) Delete(ctx context.Context) deploy.Result {
	ipRes := &alieipv1alpha1.EIPAddress{
		ObjectMeta: metav1.ObjectMeta{
			Name: n.getIPName(),
		},
	}
	if err := n.Local.Get(ctx, client.ObjectKeyFromObject(ipRes), ipRes); err != nil {
		if apierrors.IsNotFound(err) {
			return deploy.Done()
		}
		return deploy.Panic(err)
	}
	if n.shouldRemoveCommonBandwidthPackage(ipRes) {
		if err := n.removeCommonBandwidthPackageIpFn(ipRes); err != nil {
			if !isIpNotInCbwpError(err) {
				return deploy.Error(err)
			}
			n.Logger.Info("EIP is not in common bandwidth package during delete, skipping removal error",
				"eip", *ipRes.Status.AtProvider.AllocationID)
		}
	}

	if err := controllers.DeleteUpboundResource(ctx, n.Local, ipRes); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	return deploy.Done()
}

func (n *ipAddressTask) PostDelete(ctx context.Context) deploy.Result {
	return deploy.ErrorOrDone(k8s.GetDeletedObject(
		ctx, n.Local, types.NamespacedName{Name: n.getIPName()}, &alieipv1alpha1.EIPAddress{}))
}

func (n *ipAddressTask) shouldAttachCommonBandwidthPackage(ip *alieipv1alpha1.EIPAddress) bool {
	if n.isCommonBandwidthAttached(ip) || ip.Status.AtProvider.AllocationID == nil || n.VPC.Spec.AccountRef == nil {
		return false
	}
	pid, ok := n.Config.InternetConfig.AliCloudCommonBandwidthPackageIDMap[n.VPC.Spec.AccountRef.AccountID]
	return ok && pid != ""
}

func (n *ipAddressTask) isCommonBandwidthAttached(ip *alieipv1alpha1.EIPAddress) bool {
	return ip.GetAnnotations()[annotationCommonBandwidthAttachedKey] == annotationCommonBandwidthAttachedValue
}

func (n *ipAddressTask) shouldRemoveCommonBandwidthPackage(ip *alieipv1alpha1.EIPAddress) bool {
	if !n.isCommonBandwidthAttached(ip) || ip.Status.AtProvider.AllocationID == nil || n.VPC.Spec.AccountRef == nil {
		return false
	}
	pid, ok := n.Config.InternetConfig.AliCloudCommonBandwidthPackageIDMap[n.VPC.Spec.AccountRef.AccountID]
	return ok && pid != ""
}

func (n *ipAddressTask) markCommonBandwidthAttached(ctx context.Context, ip *alieipv1alpha1.EIPAddress) error {
	return retry.RetryOnConflict(retry.DefaultRetry, func() error {
		latest := &alieipv1alpha1.EIPAddress{}
		if err := n.Local.Get(ctx, client.ObjectKeyFromObject(ip), latest); err != nil {
			return err
		}
		if n.isCommonBandwidthAttached(latest) {
			// Already marked by another reconcile loop
			return nil
		}
		base := latest.DeepCopy()
		if latest.Annotations == nil {
			latest.Annotations = map[string]string{}
		}
		latest.Annotations[annotationCommonBandwidthAttachedKey] = annotationCommonBandwidthAttachedValue
		return n.Local.Patch(ctx, latest, client.MergeFrom(base))
	})
}

func (n *ipAddressTask) addCommonBandwidthPackageIp(ip *alieipv1alpha1.EIPAddress) (bool, error) {
	pid, ok := n.Config.InternetConfig.AliCloudCommonBandwidthPackageIDMap[n.VPC.Spec.AccountRef.AccountID]
	if ok && pid != "" && ip.Status.AtProvider.AllocationID != nil {
		allocationID := *ip.Status.AtProvider.AllocationID
		n.Logger.Info("Adding EIP to common bandwidth package",
			"eip", allocationID,
			"bandwidthPackageId", pid,
			"region", n.Internet.Spec.Region,
			"accountId", n.VPC.Spec.AccountRef.AccountID)

		vpcCli, err := n.aliyunCli.VPC(&aliyun.AssumedRole{
			RoleARN: util.GetRAMRoleARN(n.VPC.Spec.AccountRef.AccountID, n.Config.DataPlaneRoleName),
		}, aliyun.WithRegion(n.Internet.Spec.Region))
		if err != nil {
			return false, fmt.Errorf("create vpc client error: %w", err)
		}

		_, err = vpcCli.AddCommonBandwidthPackageIp(&vpc.AddCommonBandwidthPackageIpRequest{
			BandwidthPackageId: ptr.To(pid),
			IpInstanceId:       ip.Status.AtProvider.AllocationID,
			IpType:             ptr.To("EIP"),
			RegionId:           ptr.To(n.Internet.Spec.Region),
		})
		if err != nil {
			return false, fmt.Errorf("failed to add eip to common bandwidth package: %w", err)
		}

		n.Logger.Info("Successfully added EIP to common bandwidth package",
			"eip", allocationID,
			"bandwidthPackageId", pid)
		return true, nil
	}
	return false, nil
}

func (n *ipAddressTask) removeCommonBandwidthPackageIp(ip *alieipv1alpha1.EIPAddress) error {
	pid, ok := n.Config.InternetConfig.AliCloudCommonBandwidthPackageIDMap[n.VPC.Spec.AccountRef.AccountID]
	if ok && pid != "" && ip.Status.AtProvider.AllocationID != nil {
		allocationID := *ip.Status.AtProvider.AllocationID
		n.Logger.Info("Removing EIP from common bandwidth package",
			"eip", allocationID,
			"bandwidthPackageId", pid,
			"region", n.Internet.Spec.Region,
			"accountId", n.VPC.Spec.AccountRef.AccountID)

		vpcCli, err := n.aliyunCli.VPC(&aliyun.AssumedRole{
			RoleARN: util.GetRAMRoleARN(n.VPC.Spec.AccountRef.AccountID, n.Config.DataPlaneRoleName),
		}, aliyun.WithRegion(n.Internet.Spec.Region))
		if err != nil {
			return fmt.Errorf("create vpc client error: %w", err)
		}

		_, err = vpcCli.RemoveCommonBandwidthPackageIp(&vpc.RemoveCommonBandwidthPackageIpRequest{
			BandwidthPackageId: ptr.To(pid),
			IpInstanceId:       ip.Status.AtProvider.AllocationID,
			RegionId:           ptr.To(n.Internet.Spec.Region),
		})
		if err != nil {
			if isIpNotInCbwpError(err) {
				n.Logger.Info("EIP is not in common bandwidth package, skipping removal",
					"eip", allocationID,
					"bandwidthPackageId", pid)
				return nil
			}
			return fmt.Errorf("failed to remove eip from common bandwidth package: %w", err)
		}

		n.Logger.Info("Successfully removed EIP from common bandwidth package",
			"eip", allocationID,
			"bandwidthPackageId", pid)
	}
	return nil
}

func isAlreadyInBandwidthPackageError(err error) bool {
	if err == nil {
		return false
	}
	var sdkErr *tea.SDKError
	if errors.As(err, &sdkErr) {
		if tea.StringValue(sdkErr.Code) == "IpInstanceId.AlreadyInBandwidthPackage" {
			return true
		}
	}
	return strings.Contains(err.Error(), "IpInstanceId.AlreadyInBandwidthPackage")
}

func isIpNotInCbwpError(err error) bool {
	if err == nil {
		return false
	}
	var sdkErr *tea.SDKError
	if errors.As(err, &sdkErr) {
		if tea.StringValue(sdkErr.Code) == "OperationUnsupported.IpNotInCbwp" {
			return true
		}
	}
	return strings.Contains(err.Error(), "OperationUnsupported.IpNotInCbwp")
}
