package server

import (
	"context"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	dplanemgrv1 "github.com/tidbcloud/cluster-service-ng/api/dataplane-manager/v1"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/cert"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

func (s *serverImpl) ForceUpdateGatewayIdentity(ctx context.Context, tidbClusterId, gatewayIdentity string) error {
	if tidbClusterId == "" {
		return status.Errorf(codes.InvalidArgument, "tidb cluster id is required")
	}

	if gatewayIdentity == "" {
		return status.Errorf(codes.InvalidArgument, "gateway identity is required")
	}

	if err := validateDNSIdentity(gatewayIdentity); err != nil {
		return status.Errorf(codes.InvalidArgument, "failed to validate gateway identity: %s", err)
	}

	return s.transactioner.Transaction(ctx, func(ctx context.Context) error {
		currentCluster, err := s.tidbClusterRepo.Load(ctx, tidbClusterId, false)
		if err != nil {
			if status.Code(err) == codes.NotFound {
				return status.Errorf(codes.NotFound, "tidb cluster %s not found", tidbClusterId)
			}
			return status.Errorf(codes.Internal, "failed to load tidb cluster: %s", err)
		}

		unlockFunc, err := s.lock(ctx, currentCluster.GetSharedPoolId())
		if err != nil {
			tilog.Error(ctx, "failed to lock sharedPool",
				zap.String("sharedPoolId", currentCluster.GetSharedPoolId()),
				zap.Error(err))
			return err
		}
		defer func() {
			if err := unlockFunc(); err != nil {
				tilog.Error(ctx, "failed to unlock sharedPool",
					zap.String("sharedPoolId", currentCluster.GetSharedPoolId()),
					zap.Error(err))
			}
		}()

		tidbCluster, err := s.GetTiDBCluster(ctx, tidbClusterId)
		if err != nil {
			return err
		}

		if tidbCluster.GetPhase() != apiv1.Cluster_AVAILABLE {
			return status.Errorf(codes.FailedPrecondition,
				"tidb cluster %s phase must be AVAILABLE, got %s",
				tidbClusterId,
				tidbCluster.GetPhase().String(),
			)
		}

		tidbGroups, err := s.ListTiDBGroups(ctx, tidbClusterId)
		if err != nil {
			return err
		}
		if len(tidbGroups) > 0 {
			return status.Errorf(codes.FailedPrecondition,
				"tidb cluster %s must not have any tidb groups",
				tidbClusterId,
			)
		}

		sharedPool, err := s.sharedPoolRepo.Load(ctx, tidbCluster.GetSharedPoolId(), false)
		if err != nil {
			if status.Code(err) == codes.NotFound {
				return status.Errorf(codes.NotFound, "shared pool %s not found", tidbCluster.GetSharedPoolId())
			}
			return status.Errorf(codes.Internal, "failed to load shared pool: %s", err)
		}
		if err := guardSharedPoolMutation(sharedPool, sharedPoolMutationBlockedWhilePaused); err != nil {
			return err
		}

		tidbCluster.GatewayIdentity = gatewayIdentity
		if err := s.tidbClusterRepo.Save(ctx, tidbCluster); err != nil {
			return status.Errorf(codes.Internal, "failed to save tidb cluster: %s", err)
		}

		return s.updateTiProxyServerCert(ctx, sharedPool, tidbCluster)
	})
}

func (s *serverImpl) updateTiProxyServerCert(ctx context.Context, sharedPool *entity.SharedPool, tidbCluster *entity.TiDBCluster) error {
	remoteCli, err := s.dataplaneManager.GetClient(sharedPool.GetDataplaneName())
	if err != nil {
		return status.Errorf(codes.Internal, "failed to get remote client for shared pool %s: %s", sharedPool.GetId(), err)
	}

	dataplane, err := s.dataplaneClient.GetDataPlane(ctx, &dplanemgrv1.GetDataPlaneRequest{
		DataPlaneId: sharedPool.GetDataplaneName(),
	})
	if err != nil {
		return status.Errorf(codes.Internal, "failed to get dataplane %s: %s", sharedPool.GetDataplaneName(), err)
	}

	enablePCA := false
	var certOpts []cert.Option
	if s.bizCfg != nil {
		enablePCA = s.bizCfg.EnablePca
		if s.bizCfg.CertificateDuration.HasOverride() {
			certOpts = append(certOpts, cert.WithDurationConfig(cert.DurationConfig{
				TiDBServerDuration:    s.bizCfg.CertificateDuration.TiDBServer,
				TiProxyServerDuration: s.bizCfg.CertificateDuration.TiProxyServer,
				RenewBefore:           s.bizCfg.CertificateDuration.RenewBefore,
			}))
		}
	}

	tiproxyServerCert := cert.TiProxyServerCerts(
		tidbCluster.GetId(),
		sharedPool.GetDataPlaneNamespace(),
		tidbCluster.GetGatewayIdentity(),
		dataplane.GetDomain(),
		enablePCA,
		certOpts...,
	)
	toCr := tiproxyServerCert.DeepCopy()
	res, err := controllerutil.CreateOrUpdate(ctx, remoteCli, toCr, func() error {
		toCr.Spec = tiproxyServerCert.Spec
		return nil
	})
	if err != nil {
		tilog.Error(ctx, "failed to update tiproxy server cert",
			zap.String("clusterId", tidbCluster.GetId()),
			zap.String("certName", tiproxyServerCert.GetName()),
			zap.String("namespace", tiproxyServerCert.GetNamespace()),
			zap.Error(err))
		return status.Errorf(codes.Internal, "failed to update tiproxy server cert: %s", err)
	}

	tilog.Info(ctx, "tiproxy server cert updated",
		zap.String("clusterId", tidbCluster.GetId()),
		zap.String("certName", toCr.GetName()),
		zap.String("namespace", toCr.GetNamespace()),
		zap.Any("result", res))
	return nil
}
