package debug

import (
	"github.com/pingcap/dbaas/pkg/capacity"
	"github.com/pingcap/dbaas/pkg/tidb/component"
	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/central-sdk/db"
	"github.com/tidbcloud/central-sdk/db/dbutil"
	"github.com/tidbcloud/central-sdk/db/entity"
	"github.com/tidbcloud/central-sdk/model/admin"
	clusterutil "github.com/tidbcloud/central-sdk/model/cluster"
	"gorm.io/gorm"
)

func (m *Model) FindShootProxy(clusterID uint64) (string, error) {
	// FIXME: Currently this is completely copied from admin model.
	var (
		cluster entity.Cluster
		region  *entity.Region
		err     error
	)
	err = db.Transaction(func(tx *gorm.DB) error {
		var err error
		cluster, err = dbutil.GetCluster(tx, entity.ID(clusterID))
		if err != nil {
			return err
		}

		region, err = dbutil.GetClusterRegion(tx, &cluster)
		if err != nil {
			return errcode.NewInternalErr(errors.Errorf("cannot get cluster region for %s", cluster.Name))
		}
		return nil
	})
	if err != nil {
		return "", err
	}
	rx, errCtx := m.runtimes.Context(cluster.ProjectID.Uint64(), cluster.Provider.Name, region.Name)
	if errCtx != nil {
		return "", errcode.NewInternalErr(errors.Errorf("target infra is unavailable"))
	}
	return rx.ProxyEndpoint, nil
}

func (m *Model) ListTenantCluster(tenantID entity.ID, modifiers ...dbutil.ListClusterModifier) ([]*admin.TenantCluster, error) {
	// FIXME: Currently this is completely copied from admin model.
	var (
		clusters []entity.Cluster
		err      error
	)
	err = db.Transaction(func(tx *gorm.DB) error {
		clusters, err = dbutil.ListClusters(tx, tenantID, modifiers...)
		return err
	})
	if err != nil {
		return nil, err
	}
	var internalItems []*admin.TenantCluster
	for _, cluster := range clusters {
		var region *entity.Region
		var componentResource []component.Resources
		err = db.Transaction(func(tx *gorm.DB) error {
			_, region, err = dbutil.GetClusterProviderAndRegion(tx, cluster)
			if err != nil {
				return err
			}
			for _, cp := range cluster.Components {
				var machines []entity.ComponentMachine
				err := entity.NewComponentMachineQuerySet(tx).PreloadMachine().ComponentIDEq(cp.ID).All(&machines)
				if err != nil {
					return err
				}
				if len(machines) == 0 {
					return errors.Errorf("no any component machine for %s, cluster id: %d", cp.ComponentName, cp.ClusterID)
				}

				machine := machines[0]
				var machineProfile entity.MachineProfile
				err = entity.NewMachineProfileQuerySet(tx).IDEq(machine.Machine.MachineProfileID).One(&machineProfile)
				if err != nil {
					return err
				}

				replicas := len(machines)
				r := component.Resources{
					ComponentName: component.Name(cp.ComponentName),
					Image:         nil,
					Capacity: capacity.Capacity{
						MachineType: &machineProfile.InstanceType,
						Replicas:    uint32(replicas),
						Compute: &capacity.Compute{
							MemoryMi: machineProfile.Memory,
							CPU:      float32(machineProfile.CPU),
						},
					},
				}

				componentResource = append(componentResource, r)
			}
			return nil
		})
		internalItems = append(internalItems, m.buildInternalTenantCluster(tenantID, cluster, *region, componentResource))
	}
	return internalItems, nil
}

func (m *Model) buildInternalTenantCluster(tenantID entity.ID, cluster entity.Cluster, region entity.Region, componentResources []component.Resources) *admin.TenantCluster {
	// FIXME: Currently this is completely copied from admin model.
	c := &admin.TenantCluster{
		Id:         cluster.ID,
		Name:       cluster.Name,
		Provider:   cluster.Provider.Name.String(),
		Region:     region.Name,
		CreateTime: cluster.CreatedAt,
		Resources:  componentResources,
	}
	rx, err := m.runtimes.Context(cluster.ProjectID.Uint64(), cluster.Provider.Name, region.Name)
	if err != nil {
		//m.log.Debug("failed to get tidb cluster", zap.Uint64("clusterID", cluster.ID.Uint64()), zap.Error(err))
		return c
	}
	var ready bool
	if !cluster.Status.Creating() {
		ready = true
	}
	version, status, _ := clusterutil.CollectClusterRuntimeInfo(cluster, rx.Runtime, ready, m.log)
	c.Version = version
	c.Status = status
	return c
}
