//go:build control
// +build control

package utils

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"cloud.google.com/go/storage"
	awsconfig "github.com/aws/aws-sdk-go-v2/config"
	ec2api "github.com/aws/aws-sdk-go-v2/service/ec2"
	"github.com/aws/aws-sdk-go-v2/service/ec2/types"
	s3api "github.com/aws/aws-sdk-go-v2/service/s3"
	s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
	"github.com/google/uuid"
	"github.com/pingcap/log"
	"google.golang.org/api/iterator"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var awsClient *ec2api.Client
var s3Client *s3api.Client

const (
	awsVendor = "aws"
	gcpVendor = "gcp"
)

type AwsEndPoint struct {
	ServiceName      string              `yaml:"serviceName"`
	VpcID            string              `yaml:"vpcID"`
	SecurityGroupIds []string            `yaml:"securityGroupIds"`
	SubnetIds        map[string][]string `yaml:"subnetIds"`
	DNS              string              `yaml:"DNS"`
	VpceID           string              `yaml:"vpceID"`
}

type DataLink struct {
	PrometheusWriteDuration time.Duration `yaml:"prometheusWriteDuration"`
	AgentName               string        `yaml:"agentName"`
	AgentNameSpace          string        `yaml:"agentNameSpace"`
	PrometheusQueryURL      string        `yaml:"prometheusQueryURL"`
	PrometheusQueryPath     string        `yaml:"prometheusQueryPath"`
	PrometheusTsdbQueryPath string        `yaml:"prometheusTsdbQueryPath"`
	LogWriteDuration        time.Duration `yaml:"logWriteDuration"`
	ClusterNameSpace        string        `yaml:"clusterNameSpace"`
	BucketName              string        `yaml:"bucketName"`
	LogKeyPrefix            string        `yaml:"logKeyPrefix"`
	AgentARN                string        `yaml:"agentARN"`
}

type TSDBResponse struct {
	Status string   `json:"status"`
	Data   TSDBData `json:"data"`
}

type TSDBData struct {
	HeadStats               HeadStats   `json:"headStats"`
	SeriesCountByMetricName []TSDBCount `json:"seriesCountByMetricName"`
}

type HeadStats struct {
	NumSeries int `json:"numSeries"`
}
type TSDBCount struct {
	Name  string `json:"name"`
	Value int    `json:"value"`
}

func PrepareAwsClient() {
	if config.Service.Vendor != awsVendor {
		return
	}
	cfg, err := awsconfig.LoadDefaultConfig(context.TODO(), awsconfig.WithRegion(config.Service.Region))

	if err != nil {
		panic(err)
	}
	awsClient = ec2api.NewFromConfig(cfg)
	s3Client = s3api.NewFromConfig(cfg)
}

func GetTSDBStatus(clusterID string) TSDBResponse {
	var result TSDBResponse
	requestUrl := config.DataLink.PrometheusQueryURL + clusterID + "/" + config.DataLink.PrometheusTsdbQueryPath
	request, _ := http.NewRequest("GET", requestUrl, strings.NewReader(""))
	WithHeader(request)
	resp, err := http.DefaultClient.Do(request)
	if resp.StatusCode != 200 {
		fmt.Println("response error, code:", resp.Status)
		return result
	}
	if err != nil {
		panic(err)
	}

	respContent, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}

	err = json.Unmarshal(respContent, &result)
	if err != nil {
		panic(err)
	}

	return result
}

func CreateEndPoint(clusterID string, endPointServiceName string) {
	switch config.Service.Vendor {
	case awsVendor:
		config.AwsPVE.ServiceName = endPointServiceName
		config.AwsPVE.Create()
	case gcpVendor:
		SetEndPointDNS("svc-" + clusterID)
	}
}

func SetEndPointDNS(name string) {
	sInfo, err := kubeClient.CoreV1().Services(config.DataLink.ClusterNameSpace).Get(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		panic(err)
	}
	config.AwsPVE.DNS = sInfo.Spec.ClusterIP + ":9090"
}

func GetEndPointDNS() string {
	return config.AwsPVE.DNS
}

func GetDataLinkConfig() DataLink {
	return config.DataLink
}

func ClearResource() {
	config.AwsPVE.Delete()
}

func (awsep *AwsEndPoint) Create() {
	if awsep.ServiceName == "" {
		log.Info("awsep.ServiceName must not empty, please init config")
		return
	}
	if awsep.DNS != "" {
		log.Info("awsep.DNS exists")
		return
	}
	if _, ok := awsep.SubnetIds[awsep.ServiceName]; !ok {
		panic("aws endpoint service not exists")
	}
	dryrun := false
	ctoken := uuid.NewString()
	dnsenable := false
	param := &ec2api.CreateVpcEndpointInput{
		ServiceName:       &awsep.ServiceName,
		VpcId:             &awsep.VpcID,
		DryRun:            &dryrun,
		ClientToken:       &ctoken,
		IpAddressType:     types.IpAddressTypeIpv4,
		VpcEndpointType:   types.VpcEndpointTypeInterface,
		PrivateDnsEnabled: &dnsenable,
		SecurityGroupIds:  awsep.SecurityGroupIds,
		SubnetIds:         awsep.SubnetIds[awsep.ServiceName],
	}
	fmt.Println(param)
	vpce, err := awsClient.CreateVpcEndpoint(context.TODO(), param)
	if err != nil {
		panic(err)
	}
	if len(vpce.VpcEndpoint.DnsEntries) > 0 {
		awsep.DNS = *vpce.VpcEndpoint.DnsEntries[0].DnsName + ":80"
	}
	awsep.VpceID = *vpce.VpcEndpoint.VpcEndpointId
	fmt.Println("dns:", awsep.DNS, ", vcpeID:", awsep.VpceID)
}

func (awsep *AwsEndPoint) Delete() {
	if config.Service.Vendor != awsVendor {
		return
	}
	if awsep.VpceID == "" {
		log.Info("awsep.VpceID must not empty, please create AwsEndPoint")
		return
	}
	ids := []string{awsep.VpceID}
	dryrun := false
	param := &ec2api.DeleteVpcEndpointsInput{VpcEndpointIds: ids, DryRun: &dryrun}
	_, err := awsClient.DeleteVpcEndpoints(context.TODO(), param)
	if err != nil {
		panic(err)
	}
}

func LogCheckAndDelete(tenantID string) map[string]int64 {
	if config.Service.Vendor == "aws" {
		return AwsLogCheckAndDelete(tenantID)
	}
	return GcsLogCheckAndDelete(tenantID)
}

func AwsLogCheckAndDelete(tenantID string) map[string]int64 {
	bucketName := config.DataLink.BucketName
	prefix := fmt.Sprintf(config.DataLink.LogKeyPrefix, tenantID)

	queryParam := s3api.ListObjectsInput{
		Bucket: &bucketName,
		Prefix: &prefix,
	}

	objectList, err := s3Client.ListObjects(context.TODO(), &queryParam)
	if err != nil {
		panic(err)
	}

	sizes := make(map[string]int64)
	idList := make([]s3types.ObjectIdentifier, 0)
	for _, obj := range objectList.Contents {
		sizes[*obj.Key] = obj.Size
		idList = append(idList, s3types.ObjectIdentifier{
			Key: obj.Key,
		})
	}

	if len(idList) == 0 {
		return sizes
	}

	deleteObjParam := s3api.DeleteObjectsInput{
		Delete: &s3types.Delete{
			Objects: idList,
		},
		Bucket: &bucketName,
	}
	_, err = s3Client.DeleteObjects(context.TODO(), &deleteObjParam)
	if err != nil {
		panic(err)
	}
	return sizes

}

func GcsLogCheckAndDelete(tenantID string) map[string]int64 {
	ctx := context.Background()

	// Sets your Google Cloud Platform project ID.

	// Creates a client.
	client, err := storage.NewClient(context.TODO())
	if err != nil {
		panic(err)
	}
	defer client.Close()

	// Sets the name for the new bucket.
	bucketName := config.DataLink.BucketName

	// Creates a Bucket instance.
	bucket := client.Bucket(bucketName)
	query := storage.Query{
		Prefix: fmt.Sprintf(config.DataLink.LogKeyPrefix, tenantID),
	}
	it := bucket.Objects(ctx, &query)
	size := make(map[string]int64)
	for {
		attrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			panic(err)
		}
		size[attrs.Name] = attrs.Size
		err = bucket.Object(attrs.Name).Delete(ctx)
		if err != nil {
			panic(err)
		}
	}
	return size
}
