package deploy

import (
	"context"
	"fmt"
	"strings"

	"github.com/go-gormigrate/gormigrate/v2"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow/action"
	"github.com/tidbcloud/cluster-service-ng/pkg/cert"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	schemav1 "github.com/tidbcloud/cluster-service-ng/pkg/storage/etcd/schema/v1"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"github.com/tidbcloud/cluster-service-ng/pkg/tioperator"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/encoding/protojson"
	"gorm.io/gorm"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

func init() {
	registerMigrateRecord((*migrator).migrate20251225)
}

// update all certs for shared pools
// create tikvworker group for existing shared pools
func (m *migrator) migrate20251225(ctx context.Context) gormigrate.MigrateFunc {
	return func(db *gorm.DB) error {

		return InvokeRunMigrateWithRetry(ctx, func() error {
			return db.Transaction(func(tx *gorm.DB) error {
				sharedPools, err := m.sharedPoolRepo.List(ctx, &repo.SharedPoolFilter{Deleted: false})
				if err != nil {
					return fmt.Errorf("failed to list shared pools: %w", err)
				}

				if len(sharedPools) == 0 {
					tilog.PlainInfo("no shared pools found, skip migration")
					return nil
				}

				applyTikvWorkerAction := action.NewApplyComponentGroupAction(
					m.sharedPoolRepo,
					m.dataplaneManager,
					m.clusterConfig,
					m.dataplaneClient,
					m.templateVersion,
					&m.csngConfig.ImageConfig,
					m.csngConfig.DataPlaneMetering,
					&m.csngConfig.BizConfig,
				)

				applyHpaAction := action.NewApplyComponentHpaAction(
					m.sharedPoolRepo,
					nil, // autoscalePolicyBundleRepo: nil falls back to baseline
					m.dataplaneManager,
					m.templateVersion,
					&m.csngConfig.BizConfig,
					nil, // ruConfigRepo: nil keeps TIPROXY ScaleDown at Disabled
				)

				tilog.Info(ctx, "start to update all shared pool certs and create tikvworker groups", zap.Int("sharedPoolCount", len(sharedPools)))

				for _, sharedPool := range sharedPools {

					// do create TiKVWorkerGroup
					tilog.Info(ctx, "create TiKVWorkerGroup for shared pool", zap.String("sharedPoolId", sharedPool.GetId()))

					remoteCli, err := m.dataplaneManager.GetClient(sharedPool.GetDataplaneName())
					if err != nil {
						tilog.Error(ctx, "failed to get remote client for shared pool", zap.String("dataplaneName", sharedPool.GetDataplaneName()), zap.Error(err))
						return fmt.Errorf("failed to get remote client for shared pool: %w", err)
					}

					// check if any tikv worker group exists
					exists := false
					for _, comp := range sharedPool.GetComponentGroups() {
						if comp.GetComponentType() == apiv1.ComponentType_TIKV_WORKER || comp.GetComponentType() == apiv1.ComponentType_COPROCESSOR_WORKER {
							exists = true
							break
						}
					}
					if !exists {
						// list tikv worker by deployment
						// old tikv worker created by csng, use deployment to manage
						tikvWorkerComponents, err := listTiKVWorkerGroups(ctx, remoteCli, sharedPool.GetDataPlaneNamespace())
						if err != nil {
							tilog.Error(ctx, "failed to list tikv worker groups", zap.String("sharedPoolId", sharedPool.GetId()), zap.Error(err))
							return fmt.Errorf("failed to list tikv worker groups for shared pool %s: %w", sharedPool.GetId(), err)
						}

						if len(tikvWorkerComponents) == 0 {
							tilog.Info(ctx, "no tikv worker group found, skip create tikv worker group", zap.String("sharedPoolId", sharedPool.GetId()))
							continue
						}

						var componentTypes []string
						for _, comp := range tikvWorkerComponents {
							tilog.Info(ctx, "found tikv worker component", zap.String("componentType", comp.GetComponentType().String()), zap.String("componentId", comp.GetId()), zap.String("sharedPoolId", sharedPool.GetId()))
							comp.Phase = apiv1.Component_CREATING
							sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, comp)
							componentTypes = append(componentTypes, entity.GetComponentTypeName(comp))
						}

						sharedPoolStr, err := protojson.Marshal(sharedPool)
						if err != nil {
							return status.Errorf(codes.Internal, "failed to marshal sharedPool: %s", err)
						}

						err = applyTikvWorkerAction.Invoke(actions.ActionArgs{
							workflow.SharedPoolIdKey:   actions.ActionArgString(sharedPool.GetId()),
							workflow.SharedPoolKey:     actions.ActionArgString(string(sharedPoolStr)),
							workflow.ComponentTypesKey: actions.ActionArgString(strings.Join(componentTypes, ",")),
							workflow.ComponentIdsKey:   actions.ActionArgString(sharedPool.GetDefaultComponenId()),
							utils.TraceIdKey:           utils.GetTraceId(ctx),
						})
						if err != nil {
							tilog.Error(ctx, "failed to create tikv worker group for shared pool", zap.String("sharedPoolId", sharedPool.GetId()), zap.Error(err))
							return fmt.Errorf("failed to create tikv worker group for shared pool %s: %w", sharedPool.GetId(), err)
						}

						tilog.Info(ctx, "apply tikv worker group action completed", zap.String("sharedPoolId", sharedPool.GetId()))

						// apply hpa for tikv worker
						err = applyHpaAction.Invoke(actions.ActionArgs{
							workflow.SharedPoolIdKey:   actions.ActionArgString(sharedPool.GetId()),
							workflow.SharedPoolKey:     actions.ActionArgString(string(sharedPoolStr)),
							workflow.ComponentTypesKey: actions.ActionArgString(strings.Join(componentTypes, ",")),
							workflow.ComponentIdsKey:   actions.ActionArgString(sharedPool.GetDefaultComponenId()),
							utils.TraceIdKey:           utils.GetTraceId(ctx),
						})
						if err != nil {
							tilog.Error(ctx, "failed to apply hpa for tikv worker group", zap.String("sharedPoolId", sharedPool.GetId()), zap.Error(err))
							return fmt.Errorf("failed to apply hpa for tikv worker group for shared pool %s: %w", sharedPool.GetId(), err)
						}

						tilog.Info(ctx, "create TiKVWorkerGroup completed", zap.String("sharedPoolId", sharedPool.GetId()))

					} else {
						tilog.Info(ctx, "tikv worker group already exists, skip create tikv worker group", zap.String("sharedPoolId", sharedPool.GetId()))
					}

					tilog.Info(ctx, "do update cert for shared pool", zap.String("sharedPoolId", sharedPool.GetId()))
					certOpts := action.CertOptionsFromBizConfig(&m.csngConfig.BizConfig)
					certs := action.ComponentCerts(ctx, sharedPool, actions.ActionArgs{}, certOpts...)

					// global certs
					certs = append(certs,
						cert.ClusterClientCert(sharedPool.GetDataPlaneNamespace()),            // db-cluster-client-secret and
						cert.TiDBServerCerts(sharedPool.GetDataPlaneNamespace(), certOpts...), // global tidb server secret
						cert.TiDBComponentCert(sharedPool.GetDataPlaneNamespace()),            // global tidb component cert
						cert.TiProxyTiDBCert(sharedPool.GetDataPlaneNamespace()),              // global tidb proxy cert
					)

					tidbClusters, err := m.tidbClusterRepo.List(ctx, &repo.TiDBClusterListFilter{
						SharedPoolIds: []string{sharedPool.GetId()},
						Deleted:       false,
					})

					if err != nil {
						tilog.Error(ctx, "failed to list tidb clusters for shared pool", zap.String("sharedPoolId", sharedPool.GetId()), zap.Error(err))
						return fmt.Errorf("failed to list tidb clusters for shared pool %s: %w", sharedPool.GetId(), err)
					}

					domainSuffix, err := action.GetDomainSuffix(ctx, sharedPool, m.dataplaneClient)
					if err != nil {
						tilog.Error(ctx, "failed to get domain suffix", zap.Error(err))
						return fmt.Errorf("failed to get domain suffix: %w", err)
					}

					for _, tidbCluster := range tidbClusters {
						certs = append(certs,
							cert.TiProxyServerCerts(tidbCluster.GetId(), sharedPool.GetDataPlaneNamespace(), tidbCluster.GetGatewayIdentity(), domainSuffix, m.csngConfig.BizConfig.EnablePca, certOpts...),
						)
					}

					for _, c := range certs {
						tilog.Info(ctx, "update cert", zap.String("certName", c.Name), zap.String("namespace", c.Namespace))
						toCr := c.DeepCopy()
						res, err := controllerutil.CreateOrUpdate(ctx, remoteCli, toCr, func() error {
							toCr.Spec.DNSNames = c.Spec.DNSNames
							return nil
						})
						if err != nil {
							tilog.Error(ctx, "failed to apply create or update", zap.Any("resourceCr", toCr), zap.Error(err))
							return fmt.Errorf("failed to apply create or update: %w", err)
						}

						tilog.Info(ctx, "create or update resource success",
							zap.String("resourceName", toCr.GetName()),
							zap.Any("res", res),
						)
					}

					tilog.Info(ctx, "update cert completed", zap.String("sharedPoolId", sharedPool.GetId()))

				}
				return nil
			})
		})
	}
}

func listTiKVWorkerGroups(ctx context.Context, remoteClient client.Client, dataplaneNs string) ([]*entity.Component, error) {
	// get all component groups
	var tikvWorkerGroups appsv1.DeploymentList
	if err := remoteClient.List(
		ctx,
		&tikvWorkerGroups,
		client.InNamespace(dataplaneNs),
		client.MatchingLabels{
			"app.kubernetes.io/component": "tikv-worker",
		},
	); err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list tikv-worker groups: %s", err)
	}

	tikvWorkerComponents := make([]*entity.Component, 0, len(tikvWorkerGroups.Items))

	for i := range tikvWorkerGroups.Items {
		compEn, err := cvtTikvWorkerComponentToEn(&tikvWorkerGroups.Items[i])

		if err != nil {
			tilog.Warn(ctx, "convert tikv woker group to component", zap.Error(err))
		} else {
			tikvWorkerComponents = append(tikvWorkerComponents, compEn)
		}
	}
	return tikvWorkerComponents, nil
}

func cvtTikvWorkerComponentToEn(tikvWorkerGroup *appsv1.Deployment) (*entity.Component, error) {
	compEn := &entity.Component{
		Id: strings.Split(tikvWorkerGroup.Name, "-")[1], // db-<Id>-tikv-worker or db-<Id>-coprocessor-worker
		// ComponentType: apiv1.ComponentType_TIKV_WORKER,
	}

	switch tikvWorkerGroup.Labels[schemav1.LabelComponentKey] {
	case "tikv-worker":
		compEn.ComponentType = apiv1.ComponentType_TIKV_WORKER
	case "coprocessor-worker":
		compEn.ComponentType = apiv1.ComponentType_COPROCESSOR_WORKER
	default:
		compEn.ComponentType = apiv1.ComponentType_TIKV_WORKER
	}

	var workerContainer corev1.Container
	foundWorkerContainer := false
	for i := range tikvWorkerGroup.Spec.Template.Spec.Containers {
		if tikvWorkerGroup.Spec.Template.Spec.Containers[i].Name == "tikv-worker" {
			workerContainer = tikvWorkerGroup.Spec.Template.Spec.Containers[i]
			foundWorkerContainer = true
			break
		}
	}
	if !foundWorkerContainer {
		return nil, fmt.Errorf("container named 'tikv-worker' not found in deployment %s", tikvWorkerGroup.Name)
	}

	var err error
	compEn.NodeQuota, err = tioperator.CvtNodeSpecToQuota(
		tikvWorkerGroup.Labels,
		tikvWorkerGroup.Annotations,
		uint32(*tikvWorkerGroup.Spec.Replicas),
		workerContainer.Resources.Limits,
		nil,
	)
	if err != nil {
		return nil, fmt.Errorf("failed to get tikv worker group %s quota: %w", compEn.GetId(), err)
	}

	compEn.Version = tikvWorkerGroup.Labels[schemav1.LabelVersionKey]

	compEn.Phase = apiv1.Component_AVAILABLE

	// tikv worker does not have storage
	return compEn, nil
}
