package tidbcluster

import (
	"context"

	opv2 "github.com/pingcap/tidb-operator/api/v2/core/v1alpha1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/eks-provider/utils/tidb-operator/v2utils"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
)

type V2Getter struct {
	Cli          client.Client
	Cluster      *opv2.Cluster
	PDGroup      *opv2.PDGroup
	TiDBGroup    *opv2.TiDBGroup
	TiKVGroup    *opv2.TiKVGroup
	TiFlashGroup *opv2.TiFlashGroup
	ExtraGroups  []*opv2.TiDBGroup
}

var _ Getter = &V2Getter{}

func (g V2Getter) Name() string {
	if g.Cluster != nil {
		return g.Cluster.Name
	}
	return ""
}

func (g V2Getter) Namespace() string {
	if g.Cluster != nil {
		return g.Cluster.Namespace
	}
	return ""
}

func (g V2Getter) Version() string {
	if g.TiDBGroup != nil {
		return g.TiDBGroup.Spec.Template.Spec.Version
	}
	return ""
}

func (g V2Getter) Object() metav1.Object {
	return g.Cluster
}

func (g V2Getter) TiKVImage() string {
	// Because the image may be overlaid by the user, we need to get the actual image from the pod.
	if g.TiKVGroup != nil && g.TiKVGroup.Spec.Replicas != nil && *g.TiKVGroup.Spec.Replicas > 0 {
		return g.getImageFromPodList(g.TiKVGroup.Namespace, g.TiKVGroup.Name, opv2.LabelValComponentTiKV, g.TiKVGroup.Spec.Cluster.Name, opv2.ContainerNameTiKV)
	}
	return ""
}

func (g V2Getter) PDImage() string {
	if g.PDGroup != nil && g.PDGroup.Spec.Replicas != nil && *g.PDGroup.Spec.Replicas > 0 {
		return g.getImageFromPodList(g.PDGroup.Namespace, g.PDGroup.Name, opv2.LabelValComponentPD, g.PDGroup.Spec.Cluster.Name, opv2.ContainerNamePD)
	}
	return ""
}

// getImageFromPodList gets the image of the specified container from the pod list.
// Because the image may be overlaid by the user, we get the actual image from the pod.
func (g V2Getter) getImageFromPodList(ns, group, comp, cluster, container string) string {
	if g.Cli != nil {
		var podList corev1.PodList
		if err := g.Cli.List(context.TODO(), &podList, client.InNamespace(ns), client.MatchingLabels{
			opv2.LabelKeyGroup:     group,
			opv2.LabelKeyComponent: comp,
			opv2.LabelKeyCluster:   cluster,
		}); err == nil {
			if len(podList.Items) > 0 {
				for _, c := range podList.Items[0].Spec.Containers {
					if c.Name == container {
						return c.Image
					}
				}
			}
		}
	}
	return ""
}

func (g V2Getter) BootstrapSQLConfigMapName() string {
	if g.Cluster.Spec.BootstrapSQL != nil {
		return g.Cluster.Spec.BootstrapSQL.Name
	}
	return ""
}

func (g V2Getter) ComponentReplicas(componentType cloudv1.ComponentType) int32 {
	switch componentType {
	case cloudv1.ComponentPD:
		if g.PDGroup != nil && g.PDGroup.Spec.Replicas != nil {
			return *g.PDGroup.Spec.Replicas
		}
	case cloudv1.ComponentTiDB:
		if g.TiDBGroup != nil && g.TiDBGroup.Spec.Replicas != nil {
			return *g.TiDBGroup.Spec.Replicas
		}
	case cloudv1.ComponentTiKV:
		if g.TiKVGroup != nil && g.TiKVGroup.Spec.Replicas != nil {
			return *g.TiKVGroup.Spec.Replicas
		}
	case cloudv1.ComponentTiFlash, cloudv1.ComponentTiFlashLearner:
		if g.TiFlashGroup != nil && g.TiFlashGroup.Spec.Replicas != nil {
			return *g.TiFlashGroup.Spec.Replicas
		}
	case cloudv1.ComponentTiProxy:
		// Not supported yet
	}
	return 0
}

func (g V2Getter) ComponentDesiredReplicas(componentType cloudv1.ComponentType) int32 {
	if g.isComponentNotExistOrSuspended(componentType) {
		return 0
	}

	// as there is no auto failover in v2, we can just return the replicas count
	return g.ComponentReplicas(componentType)
}

func (g V2Getter) ComponentActualReplicas(componentType cloudv1.ComponentType) int32 {
	if g.isComponentNotExistOrSuspended(componentType) {
		return 0
	}

	switch componentType {
	case cloudv1.ComponentPD:
		if g.PDGroup != nil {
			return g.PDGroup.Status.Replicas
		}
	case cloudv1.ComponentTiDB:
		if g.TiDBGroup != nil {
			return g.TiDBGroup.Status.Replicas
		}
	case cloudv1.ComponentTiKV:
		if g.TiKVGroup != nil {
			return g.TiKVGroup.Status.Replicas
		}
	case cloudv1.ComponentTiFlash, cloudv1.ComponentTiFlashLearner:
		if g.TiFlashGroup != nil {
			return g.TiFlashGroup.Status.Replicas
		}
	case cloudv1.ComponentTiProxy:
		// Not supported yet
	}
	return 0
}

func (g V2Getter) ComponentUpdatedReplicas(componentType cloudv1.ComponentType) int32 {
	if g.isComponentNotExistOrSuspended(componentType) {
		return 0
	}

	switch componentType {
	case cloudv1.ComponentPD:
		if g.PDGroup != nil {
			return g.PDGroup.Status.UpdatedReplicas
		}
	case cloudv1.ComponentTiDB:
		if g.TiDBGroup != nil {
			return g.TiDBGroup.Status.UpdatedReplicas
		}
	case cloudv1.ComponentTiKV:
		if g.TiKVGroup != nil {
			return g.TiKVGroup.Status.UpdatedReplicas
		}
	case cloudv1.ComponentTiFlash, cloudv1.ComponentTiFlashLearner:
		if g.TiFlashGroup != nil {
			return g.TiFlashGroup.Status.UpdatedReplicas
		}
	case cloudv1.ComponentTiProxy:
		// Not supported yet
	}
	return 0
}

func (g V2Getter) ComponentReadyReplicas(componentType cloudv1.ComponentType) int32 {
	if g.isComponentNotExistOrSuspended(componentType) {
		return 0
	}

	switch componentType {
	case cloudv1.ComponentPD:
		if g.PDGroup != nil {
			return g.PDGroup.Status.ReadyReplicas
		}
	case cloudv1.ComponentTiDB:
		if g.TiDBGroup != nil {
			return g.TiDBGroup.Status.ReadyReplicas
		}
	case cloudv1.ComponentTiKV:
		if g.TiKVGroup != nil {
			return g.TiKVGroup.Status.ReadyReplicas
		}
	case cloudv1.ComponentTiFlash, cloudv1.ComponentTiFlashLearner:
		if g.TiFlashGroup != nil {
			return g.TiFlashGroup.Status.ReadyReplicas
		}
	case cloudv1.ComponentTiProxy:
		// Not supported yet
	}
	return 0
}

func (g V2Getter) isComponentNotExistOrSuspended(componentType cloudv1.ComponentType) bool {
	switch componentType {
	case cloudv1.ComponentPD:
		return g.PDGroup == nil || meta.IsStatusConditionPresentAndEqual(g.PDGroup.Status.Conditions, opv2.CondSuspended, metav1.ConditionTrue)
	case cloudv1.ComponentTiDB:
		return g.TiDBGroup == nil || meta.IsStatusConditionPresentAndEqual(g.TiDBGroup.Status.Conditions, opv2.CondSuspended, metav1.ConditionTrue)
	case cloudv1.ComponentTiKV:
		return g.TiKVGroup == nil || meta.IsStatusConditionPresentAndEqual(g.TiKVGroup.Status.Conditions, opv2.CondSuspended, metav1.ConditionTrue)
	case cloudv1.ComponentTiFlash, cloudv1.ComponentTiFlashLearner:
		return g.TiFlashGroup == nil || meta.IsStatusConditionPresentAndEqual(g.TiFlashGroup.Status.Conditions, opv2.CondSuspended, metav1.ConditionTrue)
	}
	return false
}

func (g V2Getter) ComponentAnnotations(componentType cloudv1.ComponentType) map[string]string {
	// `TcInstanceTypeAnnotationKey` annotation will be used to create Pods and is used during the scaling process.
	switch componentType {
	case cloudv1.ComponentPD:
		if g.PDGroup != nil {
			return g.PDGroup.Spec.Template.Annotations
		}
	case cloudv1.ComponentTiDB:
		if g.TiDBGroup != nil {
			return g.TiDBGroup.Spec.Template.Annotations
		}
	case cloudv1.ComponentTiKV:
		if g.TiKVGroup != nil {
			return g.TiKVGroup.Spec.Template.Annotations
		}
	case cloudv1.ComponentTiFlash, cloudv1.ComponentTiFlashLearner:
		if g.TiFlashGroup != nil {
			return g.TiFlashGroup.Spec.Template.Annotations
		}
	case cloudv1.ComponentTiProxy:
		// Not supported yet
	}
	return nil
}

func (g V2Getter) ComponentAllInstancesReady(componentType cloudv1.ComponentType) bool {
	var (
		conditions   []metav1.Condition
		generation   int64
		commonStatus opv2.CommonStatus
	)
	switch componentType {
	case cloudv1.ComponentPD:
		if g.PDGroup != nil {
			conditions = g.PDGroup.Status.Conditions
			generation = g.PDGroup.Generation
			commonStatus = g.PDGroup.Status.CommonStatus
		}
	case cloudv1.ComponentTiDB:
		if g.TiDBGroup != nil {
			conditions = g.TiDBGroup.Status.Conditions
			generation = g.TiDBGroup.Generation
			commonStatus = g.TiDBGroup.Status.CommonStatus
		}
	case cloudv1.ComponentTiKV:
		if g.TiKVGroup != nil {
			conditions = g.TiKVGroup.Status.Conditions
			generation = g.TiKVGroup.Generation
			commonStatus = g.TiKVGroup.Status.CommonStatus
		}
	case cloudv1.ComponentTiFlash:
		if g.TiFlashGroup != nil {
			conditions = g.TiFlashGroup.Status.Conditions
			generation = g.TiFlashGroup.Generation
			commonStatus = g.TiFlashGroup.Status.CommonStatus
		}
	default:
		// Not supported yet
		return false
	}

	return v2utils.ReadyAndSynced(conditions) && v2utils.IsUpToDate(generation, commonStatus)
}

func (g V2Getter) IsDeleted(ctx context.Context, cli client.Client, name, ns string) bool {
	return k8s.GetDeletedObject(ctx, cli, types.NamespacedName{Name: name, Namespace: ns}, &opv2.Cluster{}) == nil
}

func (g V2Getter) IsNil() bool {
	return g.Cluster == nil
}

func (g V2Getter) IsReady() bool {
	var ready bool
	if g.Cluster != nil {
		availCond := meta.FindStatusCondition(g.Cluster.Status.Conditions, opv2.ClusterCondAvailable)
		ready = availCond != nil && availCond.Status == metav1.ConditionTrue
	}
	if g.PDGroup != nil {
		ready = ready && meta.IsStatusConditionTrue(g.PDGroup.Status.Conditions, opv2.CondReady)
	}
	if g.TiDBGroup != nil {
		ready = ready && meta.IsStatusConditionTrue(g.TiDBGroup.Status.Conditions, opv2.CondReady)
	}
	if g.TiKVGroup != nil {
		ready = ready && meta.IsStatusConditionTrue(g.TiKVGroup.Status.Conditions, opv2.CondReady)
	}
	if g.TiFlashGroup != nil {
		ready = ready && meta.IsStatusConditionTrue(g.TiFlashGroup.Status.Conditions, opv2.CondReady)
	}
	for _, extraGroup := range g.ExtraGroups {
		ready = ready && meta.IsStatusConditionTrue(extraGroup.Status.Conditions, opv2.CondReady)
	}
	return ready
}

func (g V2Getter) IsTLSEnabled() bool {
	if g.Cluster != nil && g.Cluster.Spec.TLSCluster != nil {
		return g.Cluster.Spec.TLSCluster.Enabled
	}
	return false
}

func (g V2Getter) IsTiProxyEnabled() bool {
	return false
}

func (g V2Getter) ComponentTerminationGracePeriodSeconds(componentType cloudv1.ComponentType) *int64 {
	// TODO
	return nil
}

// TODO(ideascf): clone the returned map?
func (g V2Getter) ComponentNodeSelector(componentType cloudv1.ComponentType) map[string]string {
	var overlay *opv2.Overlay
	switch componentType {
	case cloudv1.ComponentPD:
		if g.PDGroup != nil {
			overlay = g.PDGroup.Spec.Template.Spec.Overlay
		}
	case cloudv1.ComponentTiDB:
		if g.TiDBGroup != nil {
			overlay = g.TiDBGroup.Spec.Template.Spec.Overlay
		}
	case cloudv1.ComponentTiKV:
		if g.TiKVGroup != nil {
			overlay = g.TiKVGroup.Spec.Template.Spec.Overlay
		}
	case cloudv1.ComponentTiFlash, cloudv1.ComponentTiFlashLearner:
		if g.TiFlashGroup != nil {
			overlay = g.TiFlashGroup.Spec.Template.Spec.Overlay
		}
	case cloudv1.ComponentTiProxy:
		// Not supported yet
	}

	if overlay != nil && overlay.Pod != nil && overlay.Pod.Spec != nil {
		return overlay.Pod.Spec.NodeSelector
	}
	return nil
}
