package task

import (
	"context"
	"fmt"
	"time"

	"github.com/go-logr/logr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/namer"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/repository"
)

type Context struct {
	Local                client.Client
	Logger               logr.Logger
	BeginAt              *time.Time
	Namer                *namer.Namer
	DataPlane            *v1alpha1.DataPlane
	VPC                  *v1alpha1.VPC
	VPCR                 repository.VPCReader
	PrivateLinkService   *v1alpha1.PrivateLinkService
	PrivateLinkServiceRW repository.PrivateLinkServiceReaderWriter
	Config               config.RootConfig
}

var _ deploy.Task = &BaseTask{}

func (c *Context) Default() (*Context, error) {
	// Use vpc name and namespace as the network name and namespace
	// to ensure compatibility
	c.Namer = namer.NewNamer().
		LegacyWithNetworkName(c.PrivateLinkService.Name).
		LegacyWithNetworkNamespace(c.PrivateLinkService.Namespace)

	return c, nil
}

func (c *Context) GetSubscriptionID() string {
	if c.VPC.Spec.AccountRef != nil {
		return c.VPC.Spec.AccountRef.AccountID
	}
	return ""
}

func (c *Context) ProviderConfigName() string {
	if c.VPC.Spec.AccountRef != nil {
		return c.VPC.Spec.AccountRef.ProviderConfigName
	}
	return ""
}

func (c *Context) RoleARN() string {
	if c.VPC.Spec.AccountRef != nil {
		return "arn:aws:iam::" + c.VPC.Spec.AccountRef.AccountID + ":role/" + c.VPC.Spec.AccountRef.Role
	}
	return ""
}

func (c *Context) GetZones() []string {
	if len(c.VPC.Spec.Zones) > 0 {
		return c.VPC.Spec.Zones
	}
	return c.Config.RegionConfig.Zones
}

func (c *Context) GetPrivateSubnetZoneIDs() []string {
	zones := make([]string, 0, len(c.VPC.Status.PrivateSubnets))
	for _, subnet := range c.VPC.Status.PrivateSubnets {
		zones = append(zones, subnet.AvailabilityZoneID)
	}
	return zones
}

// GetEndpointServicePort returns the primary listener port to surface in the
// EndpointServiceStatus. For multi-port LBs this is the first listener port;
// for legacy single-port LBs it is Spec.Port. Falls back to the default
// 4000 only when the LoadBalancer status has not been populated yet.
func (c *Context) GetEndpointServicePort() int32 {
	if c.PrivateLinkService.Status.LoadBalancer.Port != 0 {
		return c.PrivateLinkService.Status.LoadBalancer.Port
	}
	return v1alpha1.DefaultPrivateSQLEndpointPort
}

// GetEndpointServicePorts returns every listener port of the underlying
// LoadBalancer. Returns nil for legacy single-port LBs, where callers should
// rely on GetEndpointServicePort instead.
func (c *Context) GetEndpointServicePorts() []int32 {
	return c.PrivateLinkService.Status.LoadBalancer.Ports
}

type BaseTask struct {
	*Context
}

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

func (t *BaseTask) Category() deploy.TaskCategory {
	return deploy.ClusterTask
}

func (t *BaseTask) Create(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *BaseTask) PostCreate(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *BaseTask) Delete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

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

func (t *BaseTask) MetricsStartTime() *time.Time {
	return t.BeginAt
}

func (t *BaseTask) EffectiveCloudTags(provider string) map[string]string {
	var dataPlaneTags map[string]string
	if t.DataPlane != nil {
		dataPlaneTags = t.DataPlane.Spec.CloudTags
	}
	return controllers.EffectiveCloudTags(
		provider,
		t.Config.AdditionalCloudTags,
		t.PrivateLinkService.Labels,
		t.PrivateLinkService.Spec.Tags,
		dataPlaneTags,
	)
}

func (t *BaseTask) GetDnsProviderConfigName() string {
	return fmt.Sprintf("dns-%s", t.Config.DNSProviderConfig.AwsRoute53.AccountID)
}

func (t *BaseTask) EndpointServiceName() string {
	return controllers.UniqueIdentifier(t.PrivateLinkService.ObjectMeta, namer.WithPrefix(t.Config.ResourceNamePrefix))
}

type VpcEndpointProvider interface {
	GetAllVPCEndpointConns(ctx context.Context) ([]VpcEndpointConnection, error)
	AcceptVPCEndpoints(ctx context.Context, acceptList []VpcEndpointConnection) error
	RejectVPCEndpoints(ctx context.Context, rejectList []VpcEndpointConnection) error
}

type VpcEndpointForceDeleter interface {
	ShouldForceDeleteVPCEndpoints() bool
	DeleteVPCEndpoints(ctx context.Context, deleteList []VpcEndpointConnection) error
}

type VpcEndpointConnection struct {
	EndpointID   string
	ConnectionID string
	Phase        v1alpha1.ConnectionPhase
	Message      string
}
