package main

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"github.com/spf13/cobra"
	"github.com/tidbcloud/config-center/pkg/utils"
	pb "github.com/tidbcloud/config-center/proto/config_center"
	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"
	"google.golang.org/grpc"
	"k8s.io/utils/ptr"
)

// clusterComponentCmd represents the cluster_config command
var clusterComponentCmd = &cobra.Command{
	Use:   "cluster-component",
	Short: "manage cluster components",
	Long:  `manage cluster components`,
}

func init() {
	rootCmd.AddCommand(clusterComponentCmd)

	// clusterComponentCmd.AddCommand(newComponentAutoCreateCmd())
	clusterComponentCmd.AddCommand(newClusterComponentGetCmd())
	clusterComponentCmd.AddCommand(newClusterComponentListCmd())
	clusterComponentCmd.AddCommand(newClusterComponentUpdateCmd())
	clusterComponentCmd.AddCommand(newClusterComponentCreateCmd())
	clusterComponentCmd.AddCommand(newClusterComponentCheckCmd())
}

func newClusterComponentGetCmd() *cobra.Command {
	var clusterType, clusterID string
	cmd := &cobra.Command{
		Use:   "get <filename>",
		Short: "get cluster component",
		Long:  `get cluster component`,
		RunE: func(cmd *cobra.Command, args []string) error {
			var opts []grpc.DialOption
			opts = append(opts, grpc.WithInsecure())
			conn, err := grpc.Dial(configCenterEndpoint, opts...)
			if err != nil {
				panic(err)
			}
			defer conn.Close()
			client := pb.NewConfigCenterServiceClient(conn)

			name := args[0]
			t, err := client.GetClusterComponent(context.Background(), &pb.GetClusterComponentRequest{
				ClusterType:   pb.ClusterType(pb.ClusterType_value[clusterType]),
				ClusterId:     clusterID,
				ComponentName: name,
			})
			if err == nil {
				txt, _ := json.MarshalIndent(t, "", "    ")
				fmt.Println(string(txt))
			}
			return err
		},
	}

	cmd.Flags().StringVarP(&clusterType, "cluster-type", "T", pb.ClusterType_DEDICATED_TIDB.String(), "cluster type")
	cmd.Flags().StringVarP(&clusterID, "cluster-id", "c", "", "cluster id")

	return cmd
}

func newClusterComponentListCmd() *cobra.Command {
	var clusterType, clusterID string
	cmd := &cobra.Command{
		Use:   "list",
		Short: "list cluster components",
		Long:  `list cluster components`,
		RunE: func(cmd *cobra.Command, args []string) error {
			var opts []grpc.DialOption
			opts = append(opts, grpc.WithInsecure())
			conn, err := grpc.Dial(configCenterEndpoint, opts...)
			if err != nil {
				panic(err)
			}
			defer conn.Close()
			client := pb.NewConfigCenterServiceClient(conn)

			t, err := client.ListClusterComponents(context.Background(), &pb.ListClusterComponentsRequest{
				ClusterType: pb.ClusterType(pb.ClusterType_value[clusterType]),
				ClusterId:   clusterID,
			})
			if err == nil {
				txt, _ := json.MarshalIndent(t, "", "    ")
				fmt.Println(string(txt))
			}
			return err
		},
	}

	cmd.Flags().StringVarP(&clusterType, "cluster-type", "T", pb.ClusterType_DEDICATED_TIDB.String(), "cluster type")
	cmd.Flags().StringVarP(&clusterID, "cluster-id", "c", "", "cluster id")

	return cmd
}

func newClusterComponentCreateCmd() *cobra.Command {
	var clusterType, clusterID, version, cloudProvider string
	cmd := &cobra.Command{
		Use:   "create <component>",
		Short: "create cluster configuration file",
		Long:  `create cluster configuration file`,
		RunE: func(cmd *cobra.Command, args []string) error {
			var opts []grpc.DialOption
			opts = append(opts, grpc.WithInsecure())
			conn, err := grpc.Dial(configCenterEndpoint, opts...)
			if err != nil {
				panic(err)
			}
			defer conn.Close()
			client := pb.NewConfigCenterServiceClient(conn)

			name := args[0]
			fmt.Println(version)
			t, err := client.UpdateClusterComponent(context.Background(), &pb.UpdateClusterComponentRequest{
				ClusterType:   pb.ClusterType(pb.ClusterType_value[clusterType]),
				ClusterId:     clusterID,
				ComponentName: name,
				Version:       &version,
				CloudProvider: &cloudProvider,
				AllowMissing:  true,
			})
			if err == nil {
				txt, _ := json.MarshalIndent(t, "", "    ")
				fmt.Println(string(txt))
			}
			return err
		},
	}

	cmd.Flags().StringVarP(&clusterType, "cluster-type", "T", pb.ClusterType_DEDICATED_TIDB.String(), "cluster type")
	cmd.Flags().StringVarP(&clusterID, "cluster-id", "c", "", "cluster id")
	cmd.Flags().StringVarP(&version, "version", "v", "", "version")
	cmd.Flags().StringVarP(&cloudProvider, "cloud-provider", "p", "", "cloud provider")

	return cmd
}

func newClusterComponentUpdateCmd() *cobra.Command {
	var clusterType, clusterID, version, cloudProvider string
	cmd := &cobra.Command{
		Use:   "update <component>",
		Short: "update cluster configuration file",
		Long:  `update cluster configuration file`,
		RunE: func(cmd *cobra.Command, args []string) error {
			var opts []grpc.DialOption
			opts = append(opts, grpc.WithInsecure())
			conn, err := grpc.Dial(configCenterEndpoint, opts...)
			if err != nil {
				panic(err)
			}
			defer conn.Close()
			client := pb.NewConfigCenterServiceClient(conn)

			name := args[0]
			fmt.Println(version)
			t, err := client.UpdateClusterComponent(context.Background(), &pb.UpdateClusterComponentRequest{
				ClusterType:   pb.ClusterType(pb.ClusterType_value[clusterType]),
				ClusterId:     clusterID,
				ComponentName: name,
				Version:       &version,
				CloudProvider: &cloudProvider,
			})
			if err == nil {
				txt, _ := json.MarshalIndent(t, "", "    ")
				fmt.Println(string(txt))
			}
			return err
		},
	}

	cmd.Flags().StringVarP(&clusterType, "cluster-type", "T", pb.ClusterType_DEDICATED_TIDB.String(), "cluster type")
	cmd.Flags().StringVarP(&clusterID, "cluster-id", "c", "", "cluster id")
	cmd.Flags().StringVarP(&version, "version", "v", "", "version")
	cmd.Flags().StringVarP(&cloudProvider, "cloud-provider", "p", "", "cloud provider")

	return cmd
}

func newClusterComponentCheckCmd() *cobra.Command {
	var clusterType, clusterID, coreSvcEndpoint string
	var fix bool
	cmd := &cobra.Command{
		Use:   "check",
		Short: "check cluster components",
		Long:  `check cluster components`,
		RunE: func(cmd *cobra.Command, args []string) error {
			var opts []grpc.DialOption
			opts = append(opts, grpc.WithInsecure())
			conn, err := grpc.Dial(configCenterEndpoint, opts...)
			if err != nil {
				panic(err)
			}
			defer conn.Close()
			client := pb.NewConfigCenterServiceClient(conn)

			conn2, err := grpc.Dial(coreSvcEndpoint, opts...)
			if err != nil {
				panic(err)
			}
			coreC := core.NewCoreServiceClient(conn2)
			return checkAllComponents(client, coreC, fix)
		},
	}

	cmd.Flags().StringVarP(&clusterType, "cluster-type", "T", pb.ClusterType_DEDICATED_TIDB.String(), "cluster type")
	cmd.Flags().StringVarP(&clusterID, "cluster-id", "c", "", "cluster id")
	cmd.Flags().StringVar(&coreSvcEndpoint, "core-svc", "", "core-svc endpoint")
	cmd.Flags().BoolVarP(&fix, "fix", "f", false, "fix")

	return cmd
}

func checkAllComponents(ccc pb.ConfigCenterServiceClient, csc core.CoreServiceClient, fix bool) error {
	resp, err := csc.ListClusters(context.Background(), &core.ListClustersReq{
		Filter: &core.ClusterFilter{
			Deleted:   ptr.To(false),
			IsDevTier: ptr.To(false),
			//ExcludeStatuses: []core.ClusterStatus{core.ClusterStatus_CLUSTER_STATUS_INITIALIZING, core.ClusterStatus_CLUSTER_STATUS_PREPARING, core.ClusterStatus_CLUSTER_STATUS_PAUSED, core.ClusterStatus_CLUSTER_STATUS_SCALING},
		},
		Pager: &base.Pager{
			Page:     1,
			PageSize: 300,
		},
		WithDataPlaneInfo: true,
	})
	if err != nil {
		return err
	}
	count := len(resp.GetClusters())
	different := 0
	for clusterIDint, coreCluster := range resp.GetClusters() {
		if !coreCluster.GetAttributes().GetUseNewDedicatedArch() {
			count--
			continue
		}
		clusterID := strconv.FormatUint(clusterIDint, 10)
		meta, err := ccc.GetClusterMeta(context.Background(), &pb.GetClusterMetaRequest{
			ClusterType: pb.ClusterType_DEDICATED_TIDB,
			ClusterId:   clusterID,
		})
		if err != nil {
			panic(err)
		}
		for _, component := range resp.Clusters[clusterIDint].Components {
			compName, _ := strings.CutPrefix(component.ComponentType.String(), "COMPONENT_TYPE_")
			compName = strings.ToLower(compName)
			metaC := getComponentFromSlice(meta.Components, compName)
			metaF := getConfigFromSlice(meta.ConfigurationFiles, compName)
			if component.ComputeInstanceType != metaC.NodeInstance {
				fmt.Printf("cluster %s, component %s, node profile %s != %s\n", clusterID, compName, component.ComputeInstanceType, metaC.NodeInstance)
				different++
				if fix {
					_, err := ccc.UpdateClusterComponent(context.Background(), &pb.UpdateClusterComponentRequest{
						ClusterType:   pb.ClusterType_DEDICATED_TIDB,
						ClusterId:     clusterID,
						ComponentName: compName,
						NodeInstance:  &component.ComputeInstanceType,
					})
					if err != nil {
						panic(err)
					}
					check, err := ccc.GetClusterComponent(context.Background(), &pb.GetClusterComponentRequest{
						ClusterType:   pb.ClusterType_DEDICATED_TIDB,
						ClusterId:     clusterID,
						ComponentName: compName,
					})
					if err != nil {
						panic(err)
					}
					if check.NodeInstance != component.ComputeInstanceType || check.Version != metaC.Version || check.EffectiveImageTag != metaC.EffectiveImageTag || check.CloudProvider != metaC.CloudProvider {
						panic("fix failed")
					}
					fmt.Printf("fixed: %s\n", clusterID)
					continue
				}
				temp1, err := ccc.SearchDefaultConfigurationTemplate(context.Background(), &pb.SearchDefaultConfigurationTemplateRequest{
					ClusterType:       pb.ClusterType_DEDICATED_TIDB,
					ConfigurationType: compName,
					Version:           metaC.Version,
					CloudProvider:     metaC.CloudProvider,
					NodeInstance:      component.ComputeInstanceType,
				})
				if err != nil {
					panic(err)
				}
				temp2, err := ccc.GetConfigurationTemplate(context.Background(), &pb.GetConfigurationTemplateRequest{
					ClusterType:       pb.ClusterType_DEDICATED_TIDB,
					ConfigurationType: compName,
					TemplateId:        metaF.TemplateId,
				})
				if err != nil {
					panic(err)
				}
				fmt.Printf("current template: %d, right template: %d\n", temp2.GetTemplateId(), temp1.GetTemplateId())
				if temp1.GetTemplateId() != temp2.GetTemplateId() && utils.ParamsDiff(temp2.GetData(), temp1.GetData()) {
					fmt.Printf("current template: %s, right template: %s\n", temp2.GetData(), temp1.GetData())
				}
			}

		}
	}
	return nil
}
