package impl

import (
	"context"
	"strconv"

	"github.com/AlekSi/pointer"
	"github.com/tidbcloud/config-center/proto/config_center"
	"github.com/tidbcloud/dataflow-service/app/models/config_manager"
	"github.com/tidbcloud/dataflow-service/app/models/domain"
	"github.com/tidbcloud/dataflow-service/idl/pbgen/proto/dataflow"
	"github.com/tidbcloud/dataflow-service/pkg/grpc/grpcli"
	"github.com/tidbcloud/pkgv2/bizerr"
)

type configCenterManagerImpl struct {
	configCenterGrpcCli grpcli.ConfigCenterGrpcClient
}

func NewConfigCenterManager(configCenterGrpcCli grpcli.ConfigCenterGrpcClient) config_manager.ConfigCenterManager {
	return &configCenterManagerImpl{
		configCenterGrpcCli: configCenterGrpcCli,
	}
}

func (m *configCenterManagerImpl) GetConfigVersion(
	ctx context.Context, req *domain.GetConfigVersionReq,
) (*domain.ConfigVersionInfo, bizerr.BizError) {
	clusterIDStr := strconv.FormatUint(req.ClusterID, 10)
	componentReq := &config_center.GetClusterComponentRequest{
		ClusterType:   config_center.ClusterType_DEDICATED_TIDB,
		ClusterId:     clusterIDStr,
		ComponentName: string(req.Component),
	}
	component, bizErr := m.configCenterGrpcCli.GetClusterComponent(ctx, componentReq)
	if bizErr != nil {
		return nil, bizErr.Chain("get cluster component")
	}

	// For Compact Backup components, only need image info, skip configuration file
	if req.Component == domain.ConfigComponentCompactBackupBR ||
		req.Component == domain.ConfigComponentCompactBackupTiKV {
		return &domain.ConfigVersionInfo{
			BaseImage:      component.BaseImage,
			Tag:            component.EffectiveImageTag,
			UpdateStrategy: component.UpdateStrategy,
			Config:         "", // No configuration file needed
		}, nil
	}

	configReq := &config_center.GetClusterConfigurationFileRequest{
		ClusterType:           config_center.ClusterType_DEDICATED_TIDB,
		ClusterId:             clusterIDStr,
		ConfigurationFilename: string(req.Component),
	}
	config, bizErr := m.configCenterGrpcCli.GetClusterConfigurationFile(ctx, configReq)
	if bizErr != nil {
		return nil, bizErr.Chain("get cluster configuration")
	}
	return &domain.ConfigVersionInfo{
		BaseImage:      component.BaseImage,
		Tag:            component.EffectiveImageTag,
		UpdateStrategy: component.UpdateStrategy,
		Config:         config.Text,
	}, nil
}

func (m *configCenterManagerImpl) InitConfigVersion(
	ctx context.Context, req *domain.InitConfigVersionReq,
) bizerr.BizError {
	_, err := m.GetAndInitConfigVersion(ctx, req)
	return err
}

func (m *configCenterManagerImpl) GetAndInitConfigVersion(
	ctx context.Context, req *domain.InitConfigVersionReq,
) (*domain.ConfigVersionInfo, bizerr.BizError) {
	// For Compact Backup components, only initialize component without configuration file
	if req.Component == domain.ConfigComponentCompactBackupBR ||
		req.Component == domain.ConfigComponentCompactBackupTiKV {
		return m.initCompactBackupComponent(ctx, req)
	}

	configFormat, bizErr := domain.ConfigComponent2ConfigFormat(req.Component)
	if bizErr != nil {
		return nil, bizErr.Chain("convert config format")
	}

	templateReq := &config_center.SearchDefaultConfigurationTemplateRequest{
		ClusterType:       config_center.ClusterType_DEDICATED_TIDB,
		ConfigurationType: string(req.Component),
		Version:           req.Version,
	}
	template, bizErr := m.configCenterGrpcCli.SearchDefaultConfigurationTemplate(ctx, templateReq)
	if bizErr != nil {
		return nil, bizErr.Chain("search configuration template")
	}

	tenantParamsReq := &config_center.GetTenantInitialCustomParamsRequest{
		ClusterType:       config_center.ClusterType_DEDICATED_TIDB,
		ConfigurationType: string(req.Component),
		TenantId:          req.TenantID,
	}
	tenantParams, bizErr := m.configCenterGrpcCli.GetTenantInitialCustomParams(ctx, tenantParamsReq)
	if bizErr != nil {
		return nil, bizErr.Chain("get tenant initial custom params")
	}

	return m.initClusterConfigVersion(ctx, req, configFormat, template, tenantParams)
}

func (m *configCenterManagerImpl) initClusterConfigVersion(
	ctx context.Context,
	req *domain.InitConfigVersionReq,
	configFormat config_center.Format,
	template *config_center.ConfigurationTemplate,
	tenantParams *config_center.TenantInitialCustomPatams,
) (*domain.ConfigVersionInfo, bizerr.BizError) {
	clusterIDStr := strconv.FormatUint(req.ClusterID, 10)
	componentStr := string(req.Component)
	clusterMetaReq := &config_center.UpdateClusterMetaRequest{
		ClusterType: config_center.ClusterType_DEDICATED_TIDB,
		ClusterId:   clusterIDStr,
		ConfigurationFileRequests: []*config_center.UpdateClusterConfigurationFileRequest{
			{
				ConfigurationFilename: componentStr,
				ConfigurationType:     &componentStr,
				BelongToComponent:     &componentStr,
				Format:                &configFormat,
				TemplateId:            &template.TemplateId,
				CustomParams:          tenantParams.InitialParams,
				AllowMissing:          true,
			},
		},
		ComponentRequests: []*config_center.UpdateClusterComponentRequest{
			{
				ComponentName: string(req.Component),
				Version:       &req.Version,
				CloudProvider: pointer.ToString(string(req.CloudProvider)),
				AllowMissing:  true,
			},
		},
	}
	clusterMetaResp, bizErr := m.configCenterGrpcCli.UpdateClusterMeta(ctx, clusterMetaReq)
	if bizErr != nil && bizErr.ErrCode() != bizerr.ErrCodeRecordExist {
		return nil, bizErr.Chain("create cluster configuration and component")
	}

	var configInfo domain.ConfigVersionInfo
	if components := clusterMetaResp.GetModifiedComponents(); len(components) > 0 {
		configInfo.BaseImage = components[0].BaseImage
		configInfo.Tag = components[0].EffectiveImageTag
		configInfo.UpdateStrategy = components[0].UpdateStrategy
	}
	if configfiles := clusterMetaResp.GetModifiedConfigurationFiles(); len(configfiles) > 0 {
		configInfo.Config = configfiles[0].Text
	}

	return &configInfo, nil
}

/*
UpdateConfigVersion is used to apply configuration changes from ops-portal.
We should ensure the updated configuration is what tse has confirmed in ops-portal, so we should just forward the
information from the request to config center.
*/
//nolint:dupl
func (m *configCenterManagerImpl) UpdateConfigVersion(
	ctx context.Context, req *domain.UpdateConfigVersionReq,
) bizerr.BizError {
	clusterIDStr := strconv.FormatUint(req.ClusterID, 10)
	clusterMetaReq := &config_center.UpdateClusterMetaRequest{
		ClusterType: config_center.ClusterType_DEDICATED_TIDB,
		ClusterId:   clusterIDStr,
		ConfigurationFileRequests: []*config_center.UpdateClusterConfigurationFileRequest{
			{
				ConfigurationFilename: string(req.Component),
				CustomParams:          customParamsDataflow2ConfigCenter(req.CustomParams),
			},
		},
	}
	if req.TemplateID != nil {
		clusterMetaReq.ConfigurationFileRequests[0].TemplateId = pointer.ToInt64(int64(*req.TemplateID))
	}

	if req.Version != nil || req.CustomImageTag != nil {
		clusterMetaReq.ComponentRequests = []*config_center.UpdateClusterComponentRequest{
			{
				ComponentName:     string(req.Component),
				Version:           req.Version,
				OverwriteImageTag: req.CustomImageTag,
			},
		}
	}

	if _, bizErr := m.configCenterGrpcCli.UpdateClusterMeta(ctx, clusterMetaReq); bizErr != nil {
		return bizErr.Chain("update cluster configuration and component")
	}
	return nil
}

func (m *configCenterManagerImpl) UpdateVersion(ctx context.Context, req *domain.UpdateVersionReq) bizerr.BizError {
	clusterIDStr := strconv.FormatUint(req.ClusterID, 10)
	templateReq := &config_center.SearchDefaultConfigurationTemplateRequest{
		ClusterType:       config_center.ClusterType_DEDICATED_TIDB,
		ConfigurationType: string(req.Component),
		Version:           req.Version,
	}
	template, bizErr := m.configCenterGrpcCli.SearchDefaultConfigurationTemplate(ctx, templateReq)
	if bizErr != nil {
		return bizErr.Chain("search configuration template")
	}

	clusterMetaReq := &config_center.UpdateClusterMetaRequest{
		ClusterType: config_center.ClusterType_DEDICATED_TIDB,
		ClusterId:   clusterIDStr,
		ConfigurationFileRequests: []*config_center.UpdateClusterConfigurationFileRequest{
			{
				ConfigurationFilename: string(req.Component),
				TemplateId:            &template.TemplateId,
			},
		},
		ComponentRequests: []*config_center.UpdateClusterComponentRequest{
			{
				ComponentName: string(req.Component),
				Version:       &req.Version,
			},
		},
	}

	if _, bizErr := m.configCenterGrpcCli.UpdateClusterMeta(ctx, clusterMetaReq); bizErr != nil {
		return bizErr.Chain("update cluster configuration and component")
	}
	return nil
}

func (m *configCenterManagerImpl) initCompactBackupComponent(
	ctx context.Context,
	req *domain.InitConfigVersionReq,
) (*domain.ConfigVersionInfo, bizerr.BizError) {
	clusterIDStr := strconv.FormatUint(req.ClusterID, 10)

	// Only initialize the component, no configuration file needed
	clusterMetaReq := &config_center.UpdateClusterMetaRequest{
		ClusterType: config_center.ClusterType_DEDICATED_TIDB,
		ClusterId:   clusterIDStr,
		ComponentRequests: []*config_center.UpdateClusterComponentRequest{
			{
				ComponentName: string(req.Component),
				Version:       &req.Version,
				CloudProvider: pointer.ToString(string(req.CloudProvider)),
				AllowMissing:  true,
			},
		},
	}

	if _, bizErr := m.configCenterGrpcCli.UpdateClusterMeta(ctx, clusterMetaReq); bizErr != nil {
		return nil, bizErr.Chain("update cluster component")
	}

	// Return the component info after initialization
	getReq := &domain.GetConfigVersionReq{
		ClusterID: req.ClusterID,
		Component: req.Component,
	}
	return m.GetConfigVersion(ctx, getReq)
}

func customParamsDataflow2ConfigCenter(
	params map[string]*dataflow.ParamItem,
) map[string]*config_center.ParamItem {
	configCenterParams := make(map[string]*config_center.ParamItem, len(params))
	for k, param := range params {
		configCenterParams[k] = &config_center.ParamItem{
			Value:   param.Value,
			Comment: param.Comment,
		}
	}
	return configCenterParams
}

func (m *configCenterManagerImpl) GetComponentDefaultConfigVersion(
	ctx context.Context, req *domain.GetDefaultVersionReq,
) (*domain.DefaultVersionInfo, bizerr.BizError) {
	versionReq := &config_center.GetDefaultVersionRequest{
		ClusterType:    config_center.ClusterType_DEDICATED_TIDB,
		Component:      string(req.Component),
		ClusterVersion: &req.ClusterVersion,
		CloudProvider:  &req.ClusterProvider,
		Region:         &req.Region,
		TenantId:       &req.TenantID,
	}
	defaultVersion, bizErr := m.configCenterGrpcCli.GetDefaultVersion(ctx, versionReq)
	if bizErr != nil {
		return nil, bizErr.Chain("faield to get component default version")
	}
	return &domain.DefaultVersionInfo{
		Version: defaultVersion.Version,
		Source:  defaultVersion.Source,
	}, nil
}
