package e2e

import (
	"fmt"
	"strings"
	"time"

	"github.com/stretchr/testify/suite"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	dpmgrclient "github.com/tidbcloud/infra-provider/regional-server/test/api/dataplane-manager/client"
	"github.com/tidbcloud/infra-provider/regional-server/test/api/dataplane-manager/client/load_balancer_service"
	"github.com/tidbcloud/infra-provider/regional-server/test/api/dataplane-manager/client/private_link_connection_service"
	"github.com/tidbcloud/infra-provider/regional-server/test/api/dataplane-manager/models"
	"k8s.io/utils/pointer"
)

type loadBalancerPrivateLinkTestSuite struct {
	suite.Suite
	dataPlaneID     string
	dataPlaneClient *dpmgrclient.DataplaneManager

	lbID        string
	plServiceID string
}

func newLoadBalancerPrivateLinkTestSuite(
	dataPlaneID string,
	dataPlaneClient *dpmgrclient.DataplaneManager,
) *loadBalancerPrivateLinkTestSuite {
	return &loadBalancerPrivateLinkTestSuite{
		dataPlaneID:     dataPlaneID,
		dataPlaneClient: dataPlaneClient,
	}
}

func (s *loadBalancerPrivateLinkTestSuite) TestLoadBalancerAndPrivateLink() {
	s.Run("CreateLoadBalancer", s.CreateLoadBalancer)
	s.Run("UpdateLoadBalancer", s.UpdateLoadBalancer)
	s.Run("CreatePrivateLinkService", s.CreatePrivateLinkService)
	s.Run("DeletePrivateLinkService", s.DeletePrivateLinkService)
	s.Run("DeleteLoadBalancer", s.DeleteLoadBalancer)
}

func (s *loadBalancerPrivateLinkTestSuite) CreateLoadBalancer() {
	lbID, err := s.createLoadBalancer()
	if !s.NoError(err) {
		return
	}

	s.T().Log("lb created", lbID)
	s.lbID = lbID
	err = s.waitLoadBalancerActive()
	s.NoError(err)
}

func (s *loadBalancerPrivateLinkTestSuite) UpdateLoadBalancer() {
	if !s.NotEmpty(s.lbID) {
		return
	}

	var lb *models.Dedicatedv1LoadBalancer
	getParams := &load_balancer_service.LoadBalancerServiceGetLoadBalancerParams{
		DataPlaneID: s.dataPlaneID,
		ID:          s.lbID,
	}
	err := retryDo(s.T(), "getLoadBalancer", func() error {
		resp, err := s.dataPlaneClient.LoadBalancerService.LoadBalancerServiceGetLoadBalancer(getParams)
		if err != nil {
			return err
		}
		lb = resp.Payload
		return nil
	}, 3, time.Second*10)
	if !s.NoError(err) {
		return
	}

	tagKey := "tags.tidbcloud.com/dataplaneId"
	lb.Tags[tagKey] = s.dataPlaneID
	updateParams := &load_balancer_service.LoadBalancerServiceUpdateLoadBalancerParams{
		DataPlaneID:  s.dataPlaneID,
		ID:           s.lbID,
		Loadbalancer: lb,
	}
	var updatedLB *models.Dedicatedv1LoadBalancer
	err = retryDo(s.T(), "updateLoadBalancer", func() error {
		resp, err := s.dataPlaneClient.LoadBalancerService.LoadBalancerServiceUpdateLoadBalancer(updateParams)
		if err != nil {
			return err
		}
		updatedLB = resp.Payload
		return nil
	}, 3, time.Second*10)
	if !s.NoError(err) {
		return
	}

	s.Equal(s.dataPlaneID, updatedLB.Tags[tagKey])
}

func (s *loadBalancerPrivateLinkTestSuite) DeleteLoadBalancer() {
	if s.lbID == "" {
		s.T().Log("lb id is empty, don't need to delete")
		return
	}

	err := s.deleteLoadBalancer()
	if !s.NoError(err) {
		return
	}
	err = s.waitLoadBalancerDeleted()
	s.NoError(err)
}

func (s *loadBalancerPrivateLinkTestSuite) CreatePrivateLinkService() {
	plServiceID, err := s.createPrivateLinkService()
	if !s.NoError(err) {
		return
	}

	s.T().Log("pl service created", plServiceID)
	s.plServiceID = plServiceID
	err = s.waitPrivateLinkServiceActive()
	s.NoError(err)
}

func (s *loadBalancerPrivateLinkTestSuite) DeletePrivateLinkService() {
	if s.plServiceID == "" {
		s.T().Log("pl service id is empty, don't need to delete")
		return
	}

	err := s.deletePrivateLinkService()
	if !s.NoError(err) {
		return
	}
	err = s.waitPrivateLinkServiceDeleted()
	s.NoError(err)
}

func (s *loadBalancerPrivateLinkTestSuite) createLoadBalancer() (string, error) {
	var lbID string
	lbType := models.V1LoadBalancerTypePRIVATE
	createParams := &load_balancer_service.LoadBalancerServiceCreateLoadBalancerParams{
		DataPlaneID: s.dataPlaneID,
		ID:          s.genLBID(),
		Loadbalancer: &models.Dedicatedv1LoadBalancer{
			Identity:  pointer.String(s.genLBID()),
			Namespace: pointer.String("tidb-admin"),
			Type:      &lbType,
			Port:      pointer.Int32(80),
			Selectors: map[string]string{
				"app.kubernetes.io/instance": "dataflow-agent",
				"app.kubernetes.io/name":     "dataflow-agent",
			},
			Tags: map[string]string{
				"tags.tidbcloud.com/test":       "true",
				"tags.tidbcloud.com/created-by": "e2e",
				"tags.tidbcloud.com/tenant":     "e2e-tenant",
				consts.DataplaneLabelKey:        s.dataPlaneID,
			},
			IPWhiteList: []string{"172.16.0.0/21"},
		},
	}
	err := retryDo(s.T(), "createLoadBalancer", func() error {
		resp, err := s.dataPlaneClient.LoadBalancerService.LoadBalancerServiceCreateLoadBalancer(createParams)
		if err != nil {
			return err
		}
		lbID = resp.Payload.ID
		return nil
	}, 3, time.Second*10)
	return lbID, err
}

func (s *loadBalancerPrivateLinkTestSuite) waitLoadBalancerActive() error {
	getParams := &load_balancer_service.LoadBalancerServiceGetLoadBalancerParams{
		DataPlaneID: s.dataPlaneID,
		ID:          s.lbID,
	}
	return retryDo(s.T(), "waitLoadBalancerActive", func() error {
		resp, err := s.dataPlaneClient.LoadBalancerService.LoadBalancerServiceGetLoadBalancer(getParams)
		if err != nil {
			return err
		}
		if resp.Payload.Phase != models.V1LoadBalancerPhaseACTIVE {
			return fmt.Errorf("lb %s phase is %s, not ACTIVE", s.lbID, resp.Payload.Phase)
		}
		if !IsAllUpboundCRReady(dataPlaneDriver.GetConfig().CloudProvider,
			s.dataPlaneClient.ControlPlaneK8sProxyService,
			getLabelSelector(consts.DataplaneLabelKey, dataPlaneDriver.GetDataPlaneID())) {
			return fmt.Errorf("not all upbound cr is ready")
		}
		return nil
	}, 30, time.Second*20)
}

func (s *loadBalancerPrivateLinkTestSuite) deleteLoadBalancer() error {
	deleteParams := &load_balancer_service.LoadBalancerServiceDeleteLoadBalancerParams{
		DataPlaneID: s.dataPlaneID,
		ID:          s.lbID,
	}
	return retryDo(s.T(), "deleteLoadBalancer", func() error {
		_, err := s.dataPlaneClient.LoadBalancerService.LoadBalancerServiceDeleteLoadBalancer(deleteParams)
		return err
	}, 3, time.Second*10)
}

func (s *loadBalancerPrivateLinkTestSuite) waitLoadBalancerDeleted() error {
	getParams := &load_balancer_service.LoadBalancerServiceGetLoadBalancerParams{
		DataPlaneID: s.dataPlaneID,
		ID:          s.lbID,
	}
	return retryDo(s.T(), "waitLoadBalancerDeleted", func() error {
		resp, err := s.dataPlaneClient.LoadBalancerService.LoadBalancerServiceGetLoadBalancer(getParams)
		if err == nil {
			return fmt.Errorf("lb %s phase is %s, not deleted", s.lbID, resp.Payload.Phase)
		}
		if strings.Contains(err.Error(), "404") {
			s.T().Log("get lb deleted error", "err", err.Error())
			return nil
		}
		return err
	}, 30, time.Second*20)
}

func (s *loadBalancerPrivateLinkTestSuite) createPrivateLinkService() (string, error) {
	var plServiceID string
	createParams := &private_link_connection_service.PrivateLinkConnectionServiceCreatePrivateLinkServiceParams{
		DataPlaneID: s.dataPlaneID,
		ID:          s.genPLServiceID(),
		PrivateLinkService: &models.Dedicatedv1PrivateLinkService{
			LoadBalancerName: pointer.String(s.lbID),
			Tags: map[string]string{
				"tags.tidbcloud.com/test":       "true",
				"tags.tidbcloud.com/created-by": "e2e",
				"tags.tidbcloud.com/tenant":     "e2e-tenant",
			},
		},
	}
	err := retryDo(s.T(), "createPrivateLinkService", func() error {
		resp, err := s.dataPlaneClient.PrivateLinkConnectionService.
			PrivateLinkConnectionServiceCreatePrivateLinkService(createParams)
		if err != nil {
			return err
		}
		plServiceID = resp.Payload.ID
		return nil
	}, 3, time.Second*10)
	return plServiceID, err
}

func (s *loadBalancerPrivateLinkTestSuite) waitPrivateLinkServiceActive() error {
	getParams := &private_link_connection_service.PrivateLinkConnectionServiceGetPrivateLinkServiceParams{
		DataPlaneID: s.dataPlaneID,
		ID:          s.plServiceID,
	}
	return retryDo(s.T(), "waitPrivateLinkServiceActive", func() error {
		resp, err := s.dataPlaneClient.PrivateLinkConnectionService.
			PrivateLinkConnectionServiceGetPrivateLinkService(getParams)
		if err != nil {
			return err
		}
		if resp.Payload.Status != models.Dedicatedv1PrivateLinkServiceStatusACTIVE {
			return fmt.Errorf("pl service %s status is %s, not ACTIVE", s.plServiceID, resp.Payload.Status)
		}
		if !IsAllUpboundCRReady(dataPlaneDriver.GetConfig().CloudProvider,
			s.dataPlaneClient.ControlPlaneK8sProxyService,
			getLabelSelector(consts.DataplaneLabelKey, dataPlaneDriver.GetDataPlaneID())) {
			return fmt.Errorf("not all upbound cr is ready")
		}
		return nil
	}, 60, time.Second*20)
}

func (s *loadBalancerPrivateLinkTestSuite) deletePrivateLinkService() error {
	deleteParams := &private_link_connection_service.PrivateLinkConnectionServiceDeletePrivateLinkServiceParams{
		DataPlaneID: s.dataPlaneID,
		ID:          s.plServiceID,
	}
	return retryDo(s.T(), "deletePrivateLinkService", func() error {
		_, err := s.dataPlaneClient.PrivateLinkConnectionService.
			PrivateLinkConnectionServiceDeletePrivateLinkService(deleteParams)
		return err
	}, 3, time.Second*10)
}

func (s *loadBalancerPrivateLinkTestSuite) waitPrivateLinkServiceDeleted() error {
	getParams := &private_link_connection_service.PrivateLinkConnectionServiceGetPrivateLinkServiceParams{
		DataPlaneID: s.dataPlaneID,
		ID:          s.plServiceID,
	}
	return retryDo(s.T(), "waitPrivateLinkServiceDeleted", func() error {
		resp, err := s.dataPlaneClient.PrivateLinkConnectionService.
			PrivateLinkConnectionServiceGetPrivateLinkService(getParams)
		if err == nil {
			return fmt.Errorf("pl service %s status is %s, not deleted", s.plServiceID, resp.Payload.Status)
		}
		if strings.Contains(err.Error(), "404") {
			s.T().Log("get pl service deleted error", "err", err.Error())
			return nil
		}
		return err
	}, 30, time.Second*20)
}

func (s *loadBalancerPrivateLinkTestSuite) genLBID() string {
	return fmt.Sprintf("e2e-lb-%d", time.Now().Unix())
}

func (s *loadBalancerPrivateLinkTestSuite) genPLServiceID() string {
	return fmt.Sprintf("e2e-pl-service-%d", time.Now().Unix())
}
