package entity

import (
	"bytes"
	"fmt"
	"strings"
	"text/template"
	"time"

	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
)

var (
	computeSkuTemplate *template.Template

	diskStorageSkuTemplate      *template.Template
	diskStorageSkuTemplateAzure *template.Template
	diskIopsSkuTemplate         *template.Template
	diskThroughputSkuTemplate   *template.Template

	// New storage sku logic. Refer to https://pingcap.feishu.cn/wiki/XkTuwZ7XgivWczkxOFEcHUhHnKe.
	newDiskStorageSkuTemplate        *template.Template
	newDiskIopsSkuTemplate           *template.Template
	newDiskThroughputSkuTemplate     *template.Template
	newDiskAdditionalIopsSkuTemplate *template.Template

	tiproxySkuTemplate *template.Template
)

func init() {
	var err error
	computeSkuTemplate, err = template.New("sku-compute").
		Parse("node:{{.CloudProviderName}}:{{.RegionName}}:{{.DeployType}}:{{.ComputeInstanceType}}")
	if err != nil {
		panic(err)
	}
	diskStorageSkuTemplate, err = template.New("sku-disk-storage").
		Parse("disk-storage:{{.CloudProviderName}}:{{.RegionName}}:{{.DiskInstanceType}}")
	if err != nil {
		panic(err)
	}
	diskStorageSkuTemplateAzure, err = template.New("sku-disk-storage-azure").
		Parse("disk-storage:{{.CloudProviderName}}:{{.RegionName}}:{{.DiskInstanceType}}:{{.ComponentType}}")
	if err != nil {
		panic(err)
	}
	diskIopsSkuTemplate, err = template.New("sku-disk-iops").
		Parse("disk-iops:{{.CloudProviderName}}:{{.RegionName}}:{{.DiskInstanceType}}")
	if err != nil {
		panic(err)
	}
	diskThroughputSkuTemplate, err = template.New("sku-disk-throughput").
		Parse("disk-throughput:{{.CloudProviderName}}:{{.RegionName}}:{{.DiskInstanceType}}")
	if err != nil {
		panic(err)
	}

	newDiskStorageSkuTemplate, err = template.New("new-sku-disk-storage").
		Parse("disk-storage:{{.CloudProviderName}}:{{.RegionName}}:{{.StorageType}}:{{.DiskType}}:{{.VolumeType}}")
	if err != nil {
		panic(err)
	}
	newDiskIopsSkuTemplate, err = template.New("new-sku-disk-iops").
		Parse("disk-iops:{{.CloudProviderName}}:{{.RegionName}}:{{.StorageType}}:{{.DiskType}}:{{.VolumeType}}")
	if err != nil {
		panic(err)
	}
	newDiskThroughputSkuTemplate, err = template.New("new-sku-disk-throughput").
		Parse("disk-throughput:{{.CloudProviderName}}:{{.RegionName}}:{{.StorageType}}:{{.DiskType}}:{{.VolumeType}}")
	if err != nil {
		panic(err)
	}
	newDiskAdditionalIopsSkuTemplate, err = template.New("new-sku-disk-additional-iops").
		Parse("disk-additional-iops:{{.CloudProviderName}}:{{.RegionName}}:{{.StorageType}}:{{.DiskType}}:{{.VolumeType}}")
	if err != nil {
		panic(err)
	}

	tiproxySkuTemplate, err = template.New("sku-tiproxy").
		Parse("tiproxy:{{.CloudProviderName}}:{{.RegionName}}:{{.DeployType}}:{{.ComputeInstanceType}}")
	if err != nil {
		panic(err)
	}
}

const (
	meteringSKUPending = "PENDING"
)

func BuildComputeSkus(
	cloudProvider, region, instanceType string, compType globaldedicatedv1.ComponentType,
	instanceState ...regionaldedicatedv1.ComponentNodeInfo_Node_State,
) map[string]uint32 {
	// If the instance is unavailable and Instance Type is empty, we should return a pending sku.
	// see https://pingcap.feishu.cn/wiki/Sq3AwSpGYikdaQkYXkmcKFyPnDd for more details.
	if instanceType == "" && len(instanceState) > 0 {
		if instanceState[0] == regionaldedicatedv1.ComponentNodeInfo_Node_UNAVAILABLE {
			instanceType = meteringSKUPending
		}
	}

	var key string

	switch compType {
	case globaldedicatedv1.ComponentType_TIPROXY:
		key = GenerateSkuNameForSku(
			cloudProvider,
			region,
			instanceType,
			"",
			"",
			globaldedicatedv1.SKUType_SKU_TYPE_TIPROXY,
		)
	default:
		key = GenerateSkuNameForSku(
			cloudProvider,
			region,
			instanceType,
			"",
			cvtComponentType(compType),
			globaldedicatedv1.SKUType_SKU_TYPE_NODE,
		)
	}

	return map[string]uint32{key: 1}
}

func BuildStorageSkus(
	cloudProvider, region, storageType, diskType, volumeType string,
	sizeGi, iops, throughputMbps, additionalIops uint32,
) map[string]uint32 {
	sizeKey := GenerateSkuNameForStorageNew(
		cloudProvider,
		region,
		storageType,
		diskType,
		volumeType,
		globaldedicatedv1.SKUType_SKU_TYPE_DISK_STORAGE,
	)
	iopsKey := GenerateSkuNameForStorageNew(
		cloudProvider,
		region,
		storageType,
		diskType,
		volumeType,
		globaldedicatedv1.SKUType_SKU_TYPE_DISK_IOPS,
	)
	throughputKey := GenerateSkuNameForStorageNew(
		cloudProvider,
		region,
		storageType,
		diskType,
		volumeType,
		globaldedicatedv1.SKUType_SKU_TYPE_DISK_THROUGHPUT,
	)
	additionalIopsKey := GenerateSkuNameForStorageNew(
		cloudProvider,
		region,
		storageType,
		diskType,
		volumeType,
		globaldedicatedv1.SKUType_SKU_TYPE_DISK_ADDITIONAL_IOPS,
	)

	return map[string]uint32{
		sizeKey:           sizeGi,
		iopsKey:           iops,
		throughputKey:     throughputMbps,
		additionalIopsKey: additionalIops,
	}
}

func GenerateSkuNameForSku(
	cloudProvider, region, instanceType, volumeType, componentType string,
	skuType globaldedicatedv1.SKUType,
) string {
	b := newSkuNameBuilder(
		cloudProvider,
		region,
		"DEPLOY_TYPE_DEDICATED",
		instanceType,
		volumeType,
		componentType,
	)

	return b.Build(skuType)
}

// New storage sku logic. Refer to https://pingcap.feishu.cn/wiki/XkTuwZ7XgivWczkxOFEcHUhHnKe.
func GenerateSkuNameForStorageNew(
	cloudProvider, region, storageType, diskType, volumeType string,
	skuType globaldedicatedv1.SKUType,
) string {
	b := struct {
		CloudProviderName string
		RegionName        string
		StorageType       string
		DiskType          string
		VolumeType        string
	}{
		CloudProviderName: cloudProvider,
		RegionName:        region,
		StorageType:       storageType,
		DiskType:          diskType,
		VolumeType:        volumeType,
	}

	nameBuffer := bytes.NewBuffer(nil)
	switch skuType {
	case globaldedicatedv1.SKUType_SKU_TYPE_DISK_STORAGE:
		if err := newDiskStorageSkuTemplate.Execute(nameBuffer, b); err != nil {
			panic(fmt.Errorf("render sku name failed, skuType=%s,err=%w", skuType, err))
		}
	case globaldedicatedv1.SKUType_SKU_TYPE_DISK_IOPS:
		if err := newDiskIopsSkuTemplate.Execute(nameBuffer, b); err != nil {
			panic(fmt.Errorf("render sku name failed, skuType=%s,err=%w", skuType, err))
		}
	case globaldedicatedv1.SKUType_SKU_TYPE_DISK_THROUGHPUT:
		if err := newDiskThroughputSkuTemplate.Execute(nameBuffer, b); err != nil {
			panic(fmt.Errorf("render sku name failed, skuType=%s,err=%w", skuType, err))
		}
	case globaldedicatedv1.SKUType_SKU_TYPE_DISK_ADDITIONAL_IOPS:
		if err := newDiskAdditionalIopsSkuTemplate.Execute(nameBuffer, b); err != nil {
			panic(fmt.Errorf("render sku name failed, skuType=%s,err=%w", skuType, err))
		}
	default:
		panic(fmt.Sprintf("invalid sku type: %v", skuType))
	}

	return nameBuffer.String()
}

func newSkuNameBuilder(
	cloudProviderName string,
	regionName string,
	deployType string,
	computeInstanceType string,
	diskInstanceType string,
	componentType string,
) *skuNameBuilder {
	return &skuNameBuilder{
		CloudProviderName:   cloudProviderName,
		RegionName:          regionName,
		DeployType:          deployType,
		ComputeInstanceType: computeInstanceType,
		DiskInstanceType:    diskInstanceType,
		ComponentType:       componentType,
	}
}

type skuNameBuilder struct {
	CloudProviderName   string
	RegionName          string
	DeployType          string
	ComputeInstanceType string
	DiskInstanceType    string
	ComponentType       string
}

func (b *skuNameBuilder) Build(skuType globaldedicatedv1.SKUType) string {
	var (
		nameBuffer = bytes.NewBuffer(nil)
		err        error
	)
	switch skuType {
	case globaldedicatedv1.SKUType_SKU_TYPE_NODE:
		err = computeSkuTemplate.Execute(nameBuffer, b)
	case globaldedicatedv1.SKUType_SKU_TYPE_DISK_STORAGE:
		if strings.EqualFold(b.CloudProviderName, "azure") {
			err = diskStorageSkuTemplateAzure.Execute(nameBuffer, b)
		} else {
			err = diskStorageSkuTemplate.Execute(nameBuffer, b)
		}
	case globaldedicatedv1.SKUType_SKU_TYPE_DISK_IOPS:
		err = diskIopsSkuTemplate.Execute(nameBuffer, b)
	case globaldedicatedv1.SKUType_SKU_TYPE_DISK_THROUGHPUT:
		err = diskThroughputSkuTemplate.Execute(nameBuffer, b)
	case globaldedicatedv1.SKUType_SKU_TYPE_TIPROXY:
		err = tiproxySkuTemplate.Execute(nameBuffer, b)
	default:
		panic(fmt.Sprintf("invalid sku type: %v", skuType))
	}
	if err != nil {
		panic(fmt.Sprintf("render sku name failed, skuType=%s,err=%s", skuType, err))
	}
	return nameBuffer.String()
}

type BillingRecordStatus string

const (
	BillingRecordStatusSynced   BillingRecordStatus = "synced"
	BillingRecordStatusUnsynced BillingRecordStatus = "unsynced"
)

type ClusterMetering struct {
	ID        uint64
	TenantID  uint64
	ProjectID uint64
	ClusterID uint64
	StartTS   time.Time
	EndTs     time.Time
	Latest    bool
}

type BillingMeteringReport struct {
	CostID        string            `json:"cost_id"`
	SKU           string            `json:"sku"`
	CostTimeStamp int64             `json:"cost_time_stamp"`
	Cost          int64             `json:"cost"`
	Labels        map[string]string `json:"labels"`
	Memo          string            `json:"memo"`
}

type BillingReportRecord struct {
	ID                uint64
	CreatedAt         time.Time // readonly
	Data              []*BillingMeteringReport
	Status            BillingRecordStatus
	ClusterMeteringID uint64
	TenantID          uint64
}
