package grpc_service

import (
	"context"
	"log/slog"

	"github.com/tidbcloud/config-center/internal/db"
	"github.com/tidbcloud/config-center/internal/service"
	pb "github.com/tidbcloud/config-center/proto/config_center"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/emptypb"
)

func (c ConfigCenterService) InitClusterMeta(ctx context.Context, req *pb.InitClusterMetaRequest) (*pb.ClusterMeta, error) {
	for _, r := range req.GetComponents() {
		if r.GetValidateOnly() && !req.GetValidateOnly() {
			return nil, status.Error(codes.InvalidArgument, "validate_only should be set at InitClusterMetaRequest")
		}
	}
	for _, r := range req.GetConfigurationFiles() {
		if r.GetValidateOnly() && !req.GetValidateOnly() {
			return nil, status.Error(codes.InvalidArgument, "validate_only should be set at InitClusterMetaRequest")
		}
	}

	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	clusterMeta := &pb.ClusterMeta{
		Components:         make([]*pb.ClusterComponent, 0),
		ConfigurationFiles: make([]*pb.ConfigurationFile, 0),
	}
	var metaProfile *pb.MetaProfile
	if req.MetaProfileId != nil {
		metaProfile, err = c.UnifiedService.GetMetaProfile(ctx, &pb.GetMetaProfileRequest{
			ClusterType: req.ClusterType,
			ProfileId:   req.GetMetaProfileId(),
		})
		if err != nil {
			return nil, status.Error(codes.Internal, "cannot find meta profile")
		}
	}

	for _, c := range req.GetComponents() {
		compReq := proto.Clone(c).(*pb.CreateClusterComponentRequest)
		compReq.ValidateOnly = req.GetValidateOnly()
		if compReq.GetClusterType() == pb.ClusterType_CLUSTER_TYPE_UNSPECIFIED {
			compReq.ClusterType = req.GetClusterType()
		}
		if compReq.GetClusterId() == "" {
			compReq.ClusterId = req.GetClusterId()
		}
		if metaProfile != nil {
			if version, ok := metaProfile.ComponentVersions[compReq.GetComponentName()]; ok {
				compReq.Version = version
			}
			if tag, ok := metaProfile.ComponentImageTags[compReq.GetComponentName()]; ok {
				compReq.OverwriteImageTag = &tag
			}
		}
		comp, err := service.CreateClusterComponent(tx, compReq)
		if err != nil {
			return nil, err
		}
		clusterMeta.Components = append(clusterMeta.Components, comp)
	}

	for _, f := range req.GetConfigurationFiles() {
		cfgReq := proto.Clone(f).(*pb.CreateClusterConfigFileRequest)
		cfgReq.ValidateOnly = req.GetValidateOnly()
		if cfgReq.GetClusterType() == pb.ClusterType_CLUSTER_TYPE_UNSPECIFIED {
			cfgReq.ClusterType = req.GetClusterType()
		}
		if cfgReq.GetClusterId() == "" {
			cfgReq.ClusterId = req.GetClusterId()
		}
		if metaProfile != nil {
			applyMetaProfileSystemTemplateSource(req.GetClusterType(), cfgReq, metaProfile)

			templateID, exist := metaProfile.GetConfigCustomTemplates()[f.GetConfigurationType()]
			if exist {
				// overwrite CustomParamsSource
				cfgReq.CustomParamsSource = &pb.CreateClusterConfigFileRequest_FromCustomTemplate{
					FromCustomTemplate: &pb.FromTemplate{
						TemplateId: templateID,
					},
				}
			}
		}
		cfgFile, err := service.CreateClusterConfigFile(tx, cfgReq)
		if err != nil {
			return nil, err
		}
		clusterMeta.ConfigurationFiles = append(clusterMeta.ConfigurationFiles, cfgFile)
	}

	err = WriteAuditLog(tx, "InitClusterMeta", req.GetClusterType().String(), req.GetClusterId(), req, clusterMeta)
	if err != nil {
		return nil, status.Error(codes.Internal, "failed to write audit log")
	}

	return clusterMeta, tx.Commit()
}

func applyMetaProfileSystemTemplateSource(clusterType pb.ClusterType, cfgReq *pb.CreateClusterConfigFileRequest, metaProfile *pb.MetaProfile) {
	component := componentForMetaProfileConfigVersion(clusterType, cfgReq)
	if component == "" {
		return
	}
	version := metaProfile.GetComponentVersions()[component]
	if version == "" {
		return
	}

	fromSKU := &pb.FromSKU{}
	if current := cfgReq.GetFromSku(); current != nil {
		fromSKU = proto.Clone(current).(*pb.FromSKU)
	}
	fromSKU.Version = version
	cfgReq.SystemTemplateSource = &pb.CreateClusterConfigFileRequest_FromSku{
		FromSku: fromSKU,
	}
}

func componentForMetaProfileConfigVersion(clusterType pb.ClusterType, cfgReq *pb.CreateClusterConfigFileRequest) string {
	configType := cfgReq.GetConfigurationType()
	if configType == pb.SHARED_POOL_CONFIG_TIDB_SEM || configType == pb.SHARED_POOL_CONFIG_INITSQL {
		return pb.SHARED_POOL_COMPONENT_TIDB
	}
	return clusterType.ConfigType2Component(configType)
}

func (c ConfigCenterService) GetClusterMeta(ctx context.Context, req *pb.GetClusterMetaRequest) (*pb.ClusterMeta, error) {
	if regionalClient := c.getRegionalClientIfRoutable(req); regionalClient != nil {
		return regionalClient.GetClusterMeta(ctx, req)
	}

	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}
	components, err := service.ListClusterComponents(tx, &pb.ListClusterComponentsRequest{
		ClusterType: req.GetClusterType(),
		ClusterId:   req.GetClusterId(),
	})
	if err != nil {
		tx.Rollback()
		return nil, err
	}

	configs, err := service.ListClusterConfigurationFiles(tx, &pb.ListClusterConfigurationFilesRequest{
		ClusterType: req.GetClusterType(),
		ClusterId:   req.GetClusterId(),
	})
	if err != nil {
		tx.Rollback()
		return nil, err
	}

	if len(components.GetComponents()) == 0 && len(configs.GetFiles()) == 0 {
		tx.Rollback()
		return &pb.ClusterMeta{}, status.Errorf(codes.NotFound, "found 0 component with 0 file for %s cluster %s", req.GetClusterType().String(), req.GetClusterId())
	}

	clusterMeta := &pb.ClusterMeta{
		ConfigurationFiles: configs.GetFiles(),
		Components:         components.GetComponents(),
	}

	for _, c := range clusterMeta.GetComponents() {
		if c.GetComponentName() == req.MainComponent {
			clusterMeta.Version = c.Version
			break
		}
	}

	return clusterMeta, tx.Commit()
}

func (c ConfigCenterService) DeleteClusterMeta(ctx context.Context, req *pb.DeleteClusterMetaRequest) (*emptypb.Empty, error) {
	if regionalClient := c.getRegionalClientIfRoutable(req); regionalClient != nil {
		return regionalClient.DeleteClusterMeta(ctx, req)
	}

	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	err = deleteClusterMeta(tx, req.GetClusterType(), req.GetClusterId())
	if err != nil {
		return nil, err
	}

	resp := &emptypb.Empty{}
	err = WriteAuditLog(tx, "DeleteClusterMeta", req.GetClusterType().String(), req.GetClusterId(), req, resp)
	if err != nil {
		return nil, status.Error(codes.Internal, "failed to write audit log")
	}

	return resp, tx.Commit()
}

func (c ConfigCenterService) BatchDeleteClusterMeta(ctx context.Context, req *pb.BatchDeleteClusterMetaRequest) (*emptypb.Empty, error) {
	if regionalClient := c.getRegionalClientIfRoutable(req); regionalClient != nil {
		return regionalClient.BatchDeleteClusterMeta(ctx, req)
	}

	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	for _, clusterID := range req.GetClusterIds() {
		err = deleteClusterMeta(tx, req.GetClusterType(), clusterID)
		if err != nil {
			return nil, err
		}
	}

	resp := &emptypb.Empty{}
	err = WriteAuditLog(tx, "BatchDeleteClusterMeta", req.GetClusterType().String(), "batch", req, resp)
	if err != nil {
		return nil, status.Error(codes.Internal, "failed to write audit log")
	}

	return resp, tx.Commit()
}

func deleteClusterMeta(tx db.DB, clusterType pb.ClusterType, clusterID string) error {
	componentRows, err := tx.DeleteClusterComponentsByCluster(clusterType.String(), clusterID)
	if err != nil {
		return status.Error(codes.Internal, err.Error())
	}

	configRows, err := tx.DeleteClusterConfigInfoByCluster(clusterType.String(), clusterID)
	if err != nil {
		return status.Error(codes.Internal, err.Error())
	}

	if componentRows+configRows == 0 {
		slog.Info("delete cluster meta skipped: cluster not found or already deleted",
			"cluster_type", clusterType.String(),
			"cluster_id", clusterID,
		)
	}

	return nil
}

func (c ConfigCenterService) UpdateClusterMeta(ctx context.Context, req *pb.UpdateClusterMetaRequest) (*pb.UpdateClusterMetaResponse, error) {
	if regionalClient := c.getRegionalClientIfRoutable(req); regionalClient != nil {
		return regionalClient.UpdateClusterMeta(ctx, req)
	}

	for _, r := range req.GetComponentRequests() {
		if r.GetValidateOnly() && !req.GetValidateOnly() {
			return nil, status.Error(codes.InvalidArgument, "validate_only should be set at UpdateClusterMetaRequest")
		}
	}
	for _, r := range req.GetConfigurationFileRequests() {
		if r.GetValidateOnly() && !req.GetValidateOnly() {
			return nil, status.Error(codes.InvalidArgument, "validate_only should be set at UpdateClusterMetaRequest")
		}
	}

	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	for _, c := range req.GetComponentRequests() {
		if c.GetClusterType() == pb.ClusterType_CLUSTER_TYPE_UNSPECIFIED {
			c.ClusterType = req.GetClusterType()
		}
		if c.GetClusterId() == "" {
			c.ClusterId = req.GetClusterId()
		}
		if c.GetVersion() == "" && req.Version != nil {
			c.Version = req.Version
		}
	}

	for _, f := range req.GetConfigurationFileRequests() {
		if f.GetClusterType() == pb.ClusterType_CLUSTER_TYPE_UNSPECIFIED {
			f.ClusterType = req.GetClusterType()
		}
		if f.GetClusterId() == "" {
			f.ClusterId = req.GetClusterId()
		}
	}

	components, err := service.BatchUpdateClusterComponents(tx, &pb.BatchUpdateClusterComponentsRequest{
		Requests:     req.GetComponentRequests(),
		ValidateOnly: req.GetValidateOnly(),
	})
	if err != nil {
		return nil, err
	}

	configs, err := service.BatchUpdateClusterConfigurationFiles(tx, &pb.BatchUpdateClusterConfigurationFilesRequest{
		Requests:     req.GetConfigurationFileRequests(),
		ValidateOnly: req.GetValidateOnly(),
	})
	if err != nil {
		return nil, err
	}

	resp := pb.UpdateClusterMetaResponse{
		ModifiedComponents:         components.GetComponents(),
		ModifiedConfigurationFiles: configs.GetFiles(),
	}

	err = WriteAuditLog(tx, "UpdateClusterMeta", req.GetClusterType().String(), req.GetClusterId(), req, resp)
	if err != nil {
		return nil, status.Error(codes.Internal, "failed to write audit log")
	}

	return &resp, tx.Commit()
}

func (c ConfigCenterService) AutoUpdateClusterMeta(ctx context.Context, req *pb.AutoUpdateClusterMetaRequest) (*pb.AutoUpdateClusterMetaResponse, error) {
	if regionalClient := c.getRegionalClientIfRoutable(req); regionalClient != nil {
		return regionalClient.AutoUpdateClusterMeta(ctx, req)
	}

	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}
	for _, c := range req.GetComponentRequests() {
		if c.GetClusterType() == pb.ClusterType_CLUSTER_TYPE_UNSPECIFIED {
			c.ClusterType = req.GetClusterType()
		}
		if c.GetClusterId() == "" {
			c.ClusterId = req.GetClusterId()
		}
		if c.GetVersion() == "" && req.Version != nil {
			c.Version = req.Version
		}
	}

	for _, f := range req.GetConfigurationFileRequests() {
		if f.GetClusterType() == pb.ClusterType_CLUSTER_TYPE_UNSPECIFIED {
			f.ClusterType = req.GetClusterType()
		}
		if f.GetClusterId() == "" {
			f.ClusterId = req.GetClusterId()
		}
		if f.GetVersion() == "" && req.Version != nil {
			f.Version = req.GetVersion()
		}
	}

	components, err := service.BatchUpdateClusterComponents(tx, &pb.BatchUpdateClusterComponentsRequest{
		Requests: req.GetComponentRequests(),
	})
	if err != nil {
		tx.Rollback()
		return nil, err
	}

	configs, err := service.BatchAutoUpdateClusterConfigurationFiles(tx, &pb.BatchAutoUpdateClusterConfigurationFilesRequest{
		Requests: req.GetConfigurationFileRequests(),
	})
	if err != nil {
		tx.Rollback()
		return nil, err
	}

	resp := pb.AutoUpdateClusterMetaResponse{
		ModifiedComponents:         components.GetComponents(),
		ModifiedConfigurationFiles: configs.GetFiles(),
	}

	err = WriteAuditLog(tx, "AutoUpdateClusterMeta", req.GetClusterType().String(), req.GetClusterId(), req, resp)
	if err != nil {
		tx.Rollback()
		return nil, status.Error(codes.Internal, "failed to write audit log")
	}

	return &resp, tx.Commit()
}
