package topology

import (
	"fmt"

	clusterngv1beta "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
)

type ComponentFilter func(globalngv1.ComponentType) bool

func SharedPoolComponentFilter(componentType globalngv1.ComponentType) bool {
	switch componentType {
	case globalngv1.ComponentType_PD,
		globalngv1.ComponentType_PD_TSO,
		globalngv1.ComponentType_PD_SCHEDULING,
		globalngv1.ComponentType_TIKV,
		globalngv1.ComponentType_TIKV_WORKER,
		globalngv1.ComponentType_TIDB_WORKER,
		globalngv1.ComponentType_COPROCESSOR_WORKER,
		globalngv1.ComponentType_TIFLASH_COMPUTE,
		globalngv1.ComponentType_SCHEMA_MANAGER:
		return true
	default:
		return false
	}
}

func TiDBClusterComponentFilter(componentType globalngv1.ComponentType) bool {
	switch componentType {
	case globalngv1.ComponentType_TIPROXY:
		return true
	default:
		return false
	}
}

func TiDBGroupComponentFilter(componentType globalngv1.ComponentType) bool {
	switch componentType {
	case globalngv1.ComponentType_TIDB:
		return true
	default:
		return false
	}
}

func BuildTopologySpec(fixedTopology *globalngv1.FixedTopology, filter ComponentFilter) (*clusterngv1beta.TopologySpec, error) {
	if fixedTopology == nil || len(fixedTopology.GetComponents()) == 0 {
		return nil, fmt.Errorf("reserved capacity template fixed topology is empty")
	}
	components := make([]*clusterngv1beta.Component, 0, len(fixedTopology.GetComponents()))
	for _, component := range fixedTopology.GetComponents() {
		if filter != nil && !filter(component.GetComponentType()) {
			continue
		}
		componentType, err := cvtClusterNGComponentType(component.GetComponentType())
		if err != nil {
			return nil, err
		}
		storages, err := buildTopologyStorages(component.GetStorages())
		if err != nil {
			return nil, err
		}
		nodeQuota, err := validateNodeQuota(component)
		if err != nil {
			return nil, err
		}
		components = append(components, &clusterngv1beta.Component{
			ComponentType: componentType,
			NodeQuota: &clusterngv1beta.NodeQuota{
				NodeCount: nodeQuota.GetNodeCount(),
				NodeSpec: &clusterngv1beta.NodeSpec{
					Cpu:      nodeQuota.GetNodeSpec().GetCpu(),
					MemoryGi: nodeQuota.GetNodeSpec().GetMemoryGi(),
				},
			},
			Storages: storages,
		})
	}
	if len(components) == 0 {
		return nil, fmt.Errorf("reserved capacity template fixed topology has no matching components")
	}
	return &clusterngv1beta.TopologySpec{Components: components}, nil
}

var requiredComponents = map[globalngv1.ComponentType]int{
	globalngv1.ComponentType_PD:      3,
	globalngv1.ComponentType_TIKV:    3,
	globalngv1.ComponentType_TIDB:    1,
	globalngv1.ComponentType_TIPROXY: 1,
}

func validateNodeQuota(component *globalngv1.ComponentTopology) (*globalngv1.NodeQuota, error) {
	componentType := component.GetComponentType().String()
	nodeQuota := component.GetNodeQuota()
	if nodeQuota == nil {
		return nil, fmt.Errorf("component %s node_quota is required", componentType)
	}

	if int(nodeQuota.GetNodeCount()) < requiredComponents[component.GetComponentType()] {
		return nil, fmt.Errorf("component %s node_quota.node_count must be greater than or equal to %d",
			componentType,
			requiredComponents[component.GetComponentType()])
	}

	nodeSpec := nodeQuota.GetNodeSpec()
	if nodeSpec == nil {
		return nil, fmt.Errorf("component %s node_quota.node_spec is required", componentType)
	}
	if nodeSpec.GetCpu() == 0 {
		return nil, fmt.Errorf("component %s node_quota.node_spec.cpu must be greater than 0", componentType)
	}
	if nodeSpec.GetMemoryGi() == 0 {
		return nil, fmt.Errorf("component %s node_quota.node_spec.memory_gi must be greater than 0", componentType)
	}
	return nodeQuota, nil
}

func cvtClusterNGComponentType(componentType globalngv1.ComponentType) (clusterngv1beta.ComponentType, error) {
	switch componentType {
	case globalngv1.ComponentType_TIDB:
		return clusterngv1beta.ComponentType_TIDB, nil
	case globalngv1.ComponentType_TIKV:
		return clusterngv1beta.ComponentType_TIKV, nil
	case globalngv1.ComponentType_PD:
		return clusterngv1beta.ComponentType_PD, nil
	case globalngv1.ComponentType_TIPROXY:
		return clusterngv1beta.ComponentType_TIPROXY, nil
	case globalngv1.ComponentType_TIDB_WORKER:
		return clusterngv1beta.ComponentType_TIDB_WORKER, nil
	case globalngv1.ComponentType_TIKV_WORKER:
		return clusterngv1beta.ComponentType_TIKV_WORKER, nil
	case globalngv1.ComponentType_COPROCESSOR_WORKER:
		return clusterngv1beta.ComponentType_COPROCESSOR_WORKER, nil
	case globalngv1.ComponentType_TIFLASH_WRITE:
		return clusterngv1beta.ComponentType_TIFLASH_WRITE, nil
	case globalngv1.ComponentType_TIFLASH_COMPUTE:
		return clusterngv1beta.ComponentType_TIFLASH_COMPUTE, nil
	case globalngv1.ComponentType_PD_TSO:
		return clusterngv1beta.ComponentType_PD_TSO, nil
	case globalngv1.ComponentType_PD_SCHEDULING:
		return clusterngv1beta.ComponentType_PD_SCHEDULING, nil
	case globalngv1.ComponentType_SCHEMA_MANAGER:
		return clusterngv1beta.ComponentType_SCHEMA_MANAGER, nil
	default:
		return clusterngv1beta.ComponentType_COMPONENT_TYPE_UNSPECIFIED, fmt.Errorf("unsupported component type: %s", componentType.String())
	}
}

func buildTopologyStorages(storageSpecs []*globalngv1.StorageSpec) ([]*clusterngv1beta.Component_Storage, error) {
	storages := make([]*clusterngv1beta.Component_Storage, 0, len(storageSpecs))
	for _, storageSpec := range storageSpecs {
		name, err := cvtClusterNGStorageName(storageSpec.GetName())
		if err != nil {
			return nil, err
		}
		storages = append(storages, &clusterngv1beta.Component_Storage{
			Name:          name,
			Type:          storageSpec.GetType(),
			SizeGi:        storageSpec.GetSizeGi(),
			Iops:          storageSpec.GetIops(),
			ThroughputMbs: storageSpec.GetThroughputMbs(),
		})
	}
	return storages, nil
}

func cvtClusterNGStorageName(storageName globalngv1.StorageSpec_StorageName) (clusterngv1beta.Component_Storage_Name, error) {
	switch storageName {
	case globalngv1.StorageSpec_DATA:
		return clusterngv1beta.Component_Storage_DATA, nil
	case globalngv1.StorageSpec_RAFTLOG:
		return clusterngv1beta.Component_Storage_RAFTLOG, nil
	case globalngv1.StorageSpec_SLOWLOG:
		return clusterngv1beta.Component_Storage_SLOWLOG, nil
	default:
		return clusterngv1beta.Component_Storage_NAME_UNSPECIFIED, fmt.Errorf("unsupported storage name: %s", storageName.String())
	}
}
