package status

import (
	"fmt"
	"sort"

	"github.com/go-logr/logr"
	opv2 "github.com/pingcap/tidb-operator/api/v2/core/v1alpha1"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task/common/feature"
	"github.com/tidbcloud/eks-provider/utils/tidb-operator/v2utils"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/gitops"
)

type TiProxyResolver interface {
	ComputeTiProxyStatus(status *cloudv1.TiProxyStatus, pg *opv2.TiProxyGroup, ps []*opv2.TiProxy) (*cloudv1.TiProxyStatus, error)
}

type pgResolver struct {
	p     gitops.Provider
	gates feature.Gates

	// Now it only used to check whether all nodes are deleted.
	// It introduces an api call to fetch all nodes to update status.
	// Maybe we can avoid it to make status updation simpler
	// TODO(liubo02): avoid fetching nodes
	nodeCount int

	desiredReplicas int

	// It means cluster should suspend
	needSuspend bool

	// expected instance type
	instanceType string

	log logr.Logger
}

func NewTiProxyResolver(log logr.Logger, gates feature.Gates, p gitops.Provider, nodeCount, desiredReplicas int, needSuspend bool, instanceType string) TiProxyResolver {
	return &pgResolver{
		p:     p,
		gates: gates,

		nodeCount:       nodeCount,
		desiredReplicas: desiredReplicas,
		needSuspend:     needSuspend,
		instanceType:    instanceType,
		log:             log,
	}
}

func (r *pgResolver) ComputeTiProxyStatus(status *cloudv1.TiProxyStatus, pg *opv2.TiProxyGroup, ps []*opv2.TiProxy) (*cloudv1.TiProxyStatus, error) {
	expectedInstanceType := r.instanceType
	instanceTypeChanged := false

	members, err := r.computeMembers(ps)
	if err != nil {
		return nil, err
	}
	for _, m := range members {
		if m.InstanceType != expectedInstanceType {
			instanceTypeChanged = true
		}
	}

	if status == nil {
		status = &cloudv1.TiProxyStatus{}
	}

	instanceTypeChanged = status.InstanceTypeScalePhase != cloudv1.InstanceTypeScalePhaseNone || instanceTypeChanged

	status.Members = members
	status.Status = r.computeComponentStatus(pg, instanceTypeChanged)
	status.UpdatedReplicas = pg.Status.UpdatedReplicas
	status.Conditions = computeComponentConditions(status.Conditions, pg.Status.Conditions, r.nodeCount, r.needSuspend)

	return status, nil
}

func (r *pgResolver) computeMembers(ps []*opv2.TiProxy) ([]cloudv1.TiProxyMember, error) {
	members := make([]cloudv1.TiProxyMember, 0, len(ps))
	for _, tiproxy := range ps {
		instanceType, ok := tiproxy.Annotations[consts.TcInstanceTypeAnnotationKey]
		if !ok {
			return nil, fmt.Errorf("missing instance type annotation of the tiproxy %s/%s", tiproxy.Namespace, tiproxy.Name)
		}
		it, err := r.p.FindInstanceType(instanceType)
		if err != nil {
			return nil, fmt.Errorf("cannot get info of instance type(%s)", instanceType)
		}
		zone, ok := tiproxy.Spec.Topology[consts.ZoneLabelKey]
		if !ok {
			return nil, fmt.Errorf("missing zone of the tiproxy %s/%s", tiproxy.Namespace, tiproxy.Name)
		}

		members = append(members, cloudv1.TiProxyMember{
			Name:   InstanceNameToPodName(tiproxy.Name, "tiproxy"),
			Labels: map[string]string{cloudv1.MemberLabelAvailableZone: zone},
			Resources: cloudv1.Resources{
				CPU:    it.CPU,
				Memory: it.Memory,
			},
			Status:       computeMemberStatus(tiproxy.Status.Conditions, !tiproxy.DeletionTimestamp.IsZero()),
			InstanceType: instanceType,
		})
	}
	sort.Slice(members, func(i, j int) bool {
		return members[i].Name < members[j].Name
	})

	return members, nil
}

func (r *pgResolver) computeComponentStatus(pg *opv2.TiProxyGroup, instanceTypeChanged bool) cloudv1.ComponentStatus {
	r.log.Info("Compute tiproxy component status", "readyReplicas", pg.Status.ReadyReplicas,
		"instanceTypeChanged", instanceTypeChanged, "actualReplicas", pg.Status.Replicas, "desiredReplicas", r.desiredReplicas)
	var cs cloudv1.ComponentStatus
	switch {
	case pg.Status.ReadyReplicas == 0:
		cs = cloudv1.ComponentUnavailable
	case int(pg.Status.Replicas) != r.desiredReplicas || instanceTypeChanged:
		// when replicas is changed or instance type is changed
		cs = cloudv1.ComponentScaling
	case !v2utils.IsSynced(pg.Status.Conditions):
		cs = cloudv1.ComponentUpgrading
	case !v2utils.IsReady(pg.Status.Conditions):
		cs = cloudv1.ComponentUnhealthy
	default:
		cs = cloudv1.ComponentNormal
	}

	return cs
}
