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/bucket_service"
	"github.com/tidbcloud/infra-provider/regional-server/test/api/dataplane-manager/models"
	"github.com/tidbcloud/infra-provider/regional-server/test/e2e/driver"
)

type bucketTestSuite struct {
	suite.Suite
	dataPlaneID     string
	dataPlaneClient *dpmgrclient.DataplaneManager
	conf            *driver.RegionConfig

	bucketID string
}

func newBucketTestSuite(
	dataPlaneID string,
	dataPlaneClient *dpmgrclient.DataplaneManager,
	conf *driver.RegionConfig,
) *bucketTestSuite {
	return &bucketTestSuite{
		dataPlaneID:     dataPlaneID,
		dataPlaneClient: dataPlaneClient,
		conf:            conf,
	}
}

func (s *bucketTestSuite) TestBucket() {
	s.Run("CreateBucket", s.CreateBucket)
	s.Run("ListBuckets", s.ListBuckets)
	s.Run("DeleteBucket", s.DeleteBucket)
}

func (s *bucketTestSuite) CreateBucket() {
	bucketID, err := s.createBucket()
	if !s.NoError(err) {
		return
	}

	s.T().Log("bucket created", bucketID)
	s.bucketID = bucketID
	err = s.waitBucketReady()
	s.NoError(err)
}

func (s *bucketTestSuite) ListBuckets() {
	listParams := &bucket_service.BucketServiceListBucketsParams{
		DataplaneID: s.dataPlaneID,
	}
	var buckets []*models.V1Bucket
	err := retryDo(s.T(), "ListBuckets", func() error {
		resp, err := s.dataPlaneClient.BucketService.BucketServiceListBuckets(listParams)
		if err != nil {
			return err
		}
		buckets = resp.Payload.Buckets
		return nil
	}, 3, time.Second*10)
	if !s.NoError(err) {
		return
	}

	if s.bucketID != "" {
		s.True(s.bucketExists(buckets))
	}
}

func (s *bucketTestSuite) DeleteBucket() {
	if s.bucketID == "" {
		s.T().Log("bucket id is empty, don't need to delete")
		return
	}

	err := s.deleteBucket()
	if !s.NoError(err) {
		return
	}
	err = s.waitBucketDeleted()
	s.NoError(err)
}

func (s *bucketTestSuite) createBucket() (string, error) {
	var bucketID string
	createParams := &bucket_service.BucketServiceCreateBucketParams{
		DataplaneID: s.dataPlaneID,
		Bucket: &models.V1Bucket{
			ID:     s.genBucketID(),
			Region: s.conf.Region,
			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(), "createBucket", func() error {
		resp, err := s.dataPlaneClient.BucketService.BucketServiceCreateBucket(createParams)
		if err != nil {
			return err
		}
		bucketID = resp.Payload.ID
		return nil
	}, 3, time.Second*10)
	return bucketID, err
}

func (s *bucketTestSuite) waitBucketReady() error {
	getParams := &bucket_service.BucketServiceGetBucketParams{
		DataplaneID: s.dataPlaneID,
		BucketID:    s.bucketID,
	}
	return retryDo(s.T(), "waitBucketReady", func() error {
		resp, err := s.dataPlaneClient.BucketService.BucketServiceGetBucket(getParams)
		if err != nil {
			return err
		}
		if resp.Payload.Status != "Ready" {
			return fmt.Errorf("bucket %s status is %s, not Ready", s.bucketID, 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
	}, 30, time.Second*20)
}

func (s *bucketTestSuite) deleteBucket() error {
	deleteParams := &bucket_service.BucketServiceDeleteBucketParams{
		DataplaneID: s.dataPlaneID,
		BucketID:    s.bucketID,
	}
	return retryDo(s.T(), "deleteBucket", func() error {
		_, err := s.dataPlaneClient.BucketService.BucketServiceDeleteBucket(deleteParams)
		return err
	}, 3, time.Second*10)
}

func (s *bucketTestSuite) waitBucketDeleted() error {
	getParams := &bucket_service.BucketServiceGetBucketParams{
		DataplaneID: s.dataPlaneID,
		BucketID:    s.bucketID,
	}
	return retryDo(s.T(), "waitBucketCreated", func() error {
		resp, err := s.dataPlaneClient.BucketService.BucketServiceGetBucket(getParams)
		if err == nil {
			return fmt.Errorf("bucket %s status is %s, not deleted", s.bucketID, resp.Payload.Status)
		}
		if strings.Contains(err.Error(), "404") {
			s.T().Log("get bucket deleted error", "err", err.Error())
			return nil
		}
		return err
	}, 30, time.Second*20)
}

func (s *bucketTestSuite) bucketExists(buckets []*models.V1Bucket) bool {
	for _, bucket := range buckets {
		if bucket.ID == s.bucketID {
			return true
		}
	}
	return false
}

func (s *bucketTestSuite) genBucketID() string {
	return fmt.Sprintf("e2e-bucket-%d", time.Now().Unix())
}
