package cron

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"
	meteringsdkcommon "github.com/pingcap/metering_sdk/common"
	meteringsdkmeteringwriter "github.com/pingcap/metering_sdk/writer/metering"
	apiv1beta "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/server"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

type MeteringDiskInfoJob struct {
	ngService          server.Service
	lockRepo           repo.LockRepo
	expression         string
	meteringDataWriter *meteringsdkmeteringwriter.MeteringWriter
}

func NewMeteringDiskInfoJob(
	ngService server.Service,
	lockRepo repo.LockRepo,
	meteringDataWriter *meteringsdkmeteringwriter.MeteringWriter,
	expression string,
) *MeteringDiskInfoJob {
	return &MeteringDiskInfoJob{
		ngService:          ngService,
		lockRepo:           lockRepo,
		expression:         expression,
		meteringDataWriter: meteringDataWriter,
	}
}

func (j *MeteringDiskInfoJob) Name() string {
	return "MeteringDiskInfoJob"
}

func (j *MeteringDiskInfoJob) CronExpression() string {
	return j.expression
}

func (j *MeteringDiskInfoJob) Execute(ctx context.Context) error {
	unlockFunc, err := j.lock(ctx)
	if err != nil {
		tilog.Warn(ctx, "failed to acquire lock for metering disk info, skip this round", zap.Error(err))
		return nil
	}
	defer func() { _ = unlockFunc() }()

	// list all shared pools
	sharedPools, err := j.ngService.ListSharedPools(ctx, nil, nil)
	if err != nil {
		tilog.Error(ctx, "failed to list shared pools", zap.Error(err))
		return fmt.Errorf("failed to metering disk info: %w", err)
	}

	for _, sharedPool := range sharedPools {
		if sharedPool.Phase == apiv1beta.SharedPool_DELETING {
			continue
		}

		meteringData := &meteringsdkcommon.MeteringData{
			Timestamp:    time.Now().Unix() / 60 * 60,
			Category:     "control-plane",
			SelfID:       strings.ReplaceAll(uuid.NewString(), "-", ""),
			SharedPoolID: sharedPool.GetId(),
			Data:         make([]map[string]any, 0),
		}
		diskData := map[string]any{
			"cluster_id":  "",
			"source_name": "control-planel",
			"version":     "1",
		}
		fillDiskData(sharedPool, diskData)
		meteringData.Data = append(meteringData.Data, diskData)
		if err := j.meteringDataWriter.Write(ctx, meteringData); err != nil {
			tilog.Error(ctx, "failed to write metering data", zap.Error(err))
			return fmt.Errorf("failed to metering disk info: %w", err)
		}
	}
	return nil
}

func (s *MeteringDiskInfoJob) lock(ctx context.Context) (func() error, error) {
	key := "metering_disk_info"
	lock := &entity.Lock{
		Key:         key,
		TimeoutSecs: 10,
	}

	unlockFunc, err := s.lockRepo.Lock(ctx, lock)
	if err != nil {
		return nil, err
	}
	return unlockFunc, nil
}

type ValueUnit struct {
	Value int    `json:"value"`
	Unit  string `json:"unit"`
}

type DiskInfo struct {
	ClusterID  string               `json:"cluster_id"`
	SourceName string               `json:"source_name"`
	Version    string               `json:"version"`
	DiskData   map[string]ValueUnit `json:",inline"`
}

func fillDiskData(sharedPool *entity.SharedPool, data map[string]any) {
	for _, comp := range sharedPool.GetComponentGroups() {
		switch comp.GetComponentType() {
		case apiv1beta.ComponentType_TIKV:
			for _, storage := range comp.GetStorages() {
				if storage.GetName() == apiv1beta.Component_Storage_DATA {
					data["tikv_data_disk_iops"] = ValueUnit{
						Value: int(storage.GetIops()),
						Unit:  "iops",
					}
					data["tikv_data_disk_throughput"] = ValueUnit{
						Value: int(storage.GetThroughputMbs()),
						Unit:  "mb/s",
					}
					data["tikv_data_disk_count"] = ValueUnit{
						Value: int(comp.GetNodeQuota().GetNodeCount()),
						Unit:  "count",
					}
					data["tikv_data_disk_type"] = storage.Type
				}
				if storage.GetName() == apiv1beta.Component_Storage_RAFTLOG {
					data["tikv_log_disk_iops"] = ValueUnit{
						Value: int(storage.GetIops()),
						Unit:  "iops",
					}
					data["tikv_log_disk_throughput"] = ValueUnit{
						Value: int(storage.GetThroughputMbs()),
						Unit:  "mb/s",
					}
					data["tikv_log_disk_count"] = ValueUnit{
						Value: int(comp.GetNodeQuota().GetNodeCount()),
						Unit:  "count",
					}
					data["tikv_log_disk_type"] = storage.Type
				}
			}
		case apiv1beta.ComponentType_TIFLASH_COMPUTE:
			for _, storage := range comp.GetStorages() {
				if storage.GetName() == apiv1beta.Component_Storage_DATA {
					data["tiflash_compute_disk_iops"] = ValueUnit{
						Value: int(storage.GetIops()),
						Unit:  "iops",
					}
					data["tiflash_compute_disk_throughput"] = ValueUnit{
						Value: int(storage.GetThroughputMbs()),
						Unit:  "mb/s",
					}
					data["tiflash_compute_disk_count"] = ValueUnit{
						Value: int(comp.NodeQuota.GetNodeCount()),
						Unit:  "count",
					}
					data["tiflash_compute_disk_type"] = storage.Type
				}
			}
		case apiv1beta.ComponentType_TIFLASH_WRITE:
			for _, storage := range comp.GetStorages() {
				if storage.GetName() == apiv1beta.Component_Storage_DATA {
					data["tiflash_write_disk_iops"] = ValueUnit{
						Value: int(storage.GetIops()),
						Unit:  "iops",
					}
					data["tiflash_write_disk_throughput"] = ValueUnit{
						Value: int(storage.GetThroughputMbs()),
						Unit:  "mb/s",
					}
					data["tiflash_write_disk_count"] = ValueUnit{
						Value: int(comp.NodeQuota.GetNodeCount()),
						Unit:  "count",
					}
					data["tiflash_write_disk_type"] = storage.Type
				}
			}
		}
	}
}
