package cloudconfig

import (
	"context"
	"fmt"

	cchttpapi "github.com/tidbcloud/devops/api/cloudconfig"
	ccent "github.com/tidbcloud/devops/entity/cloudconfig"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	"github.com/tidbcloud/devops/pkg/service/cloudconfig/api"
	"github.com/tidbcloud/devops/pkg/service/cloudconfig/change/common"
	"github.com/tidbcloud/devops/pkg/service/cloudconfig/cvt"

	ccproto "github.com/tidbcloud/config-center/proto/config_center"
)

func (s *svc) ListTemplate(ctx context.Context, typ ccent.ClusterType, param api.ListTemplateParam) ([]*ccent.Template, error) {
	req := &ccproto.ListConfigurationTemplatesRequest{
		ClusterType: cvt.To.ConfigCenter.ClusterType(typ),
		Tags: map[string]string{
			ccproto.TEMPLATE_TAG_TEMPLATE_TYPE: param.TemplateType,
		},
	}
	if param.Version != "" {
		req.Tags[ccproto.TEMPLATE_TAG_EFFECTIVE_MINOR_VERSION] = param.Version
	}
	if param.TenantID != "" {
		req.Tags[ccproto.TEMPLATE_TAG_OWNER_TENANT_ID] = param.TenantID
	}
	templates, err := s.cfgCenter.ConfigAPI.ListConfigurationTemplates(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("list configuration templates failed: %v", err)
	}

	result := []*ccent.Template{}
	for _, t := range templates.GetTemplates() {
		result = append(result, cvt.From.ConfigCenter.Template(t))
	}

	return result, nil
}

func (s *svc) GetTemplate(ctx context.Context, typ ccent.ClusterType, id uint64, param api.GetTemplateParam) (*ccent.Template, error) {
	req := &ccproto.GetConfigurationTemplateRequest{
		ClusterType: cvt.To.ConfigCenter.ClusterType(typ),
		TemplateId:  int64(id),
	}
	resp, err := s.cfgCenter.ConfigAPI.GetConfigurationTemplate(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("get configuration template failed: %v", err)
	}

	template := cvt.From.ConfigCenter.Template(resp)

	if param.WithDefaultCases {
		usageReq := &ccproto.GetTemplateUsageRequest{
			ClusterType:       cvt.To.ConfigCenter.ClusterType(template.ClusterType),
			ConfigurationType: template.ConfigType,
			TemplateId:        int64(template.ID),
		}
		usage, err := s.cfgCenter.ConfigAPI.GetTemplateUsage(ctx, usageReq)
		if err != nil {
			return nil, fmt.Errorf("get configuration template usage failed: %v", err)
		}

		template.DefaultCases = container.TransformSlice(usage.DefaultCases, cvt.From.ConfigCenter.TemplateDefaultCase)
	}

	if template.Tags[ccproto.TEMPLATE_TAG_TEMPLATE_TYPE] == string(ccent.TemplateTypeCustom) {
		template.CustomTemplateInfo.Data = make(map[string]ccent.Template)

		for configT, subID := range template.Parameters {
			tempID := subID.ParameterValue.Value.(float64) // todo: check
			templatePart, err := s.cfgCenter.ConfigAPI.GetConfigurationTemplate(ctx, &ccproto.GetConfigurationTemplateRequest{
				ClusterType: cvt.To.ConfigCenter.ClusterType(typ),
				TemplateId:  int64(tempID),
			})
			if err != nil {
				return nil, fmt.Errorf("cannot get custom template part: %v", err)
			}
			template.CustomTemplateInfo.Data[configT] = *cvt.From.ConfigCenter.Template(templatePart)
		}
	}

	return template, nil
}

func (s *svc) GetTemplateByDefaultCase(ctx context.Context, typ ccent.ClusterType, defcase ccent.TemplateDefaultCase) (*ccent.Template, error) {
	req := &ccproto.SearchDefaultConfigurationTemplateRequest{
		ClusterType:       cvt.To.ConfigCenter.ClusterType(typ),
		ConfigurationType: defcase.ConfigType,
		Version:           defcase.Version,
		CloudProvider:     defcase.CloudProvider,
		NodeInstance:      defcase.InstanceType,
		TenantId:          defcase.TenantID,
	}
	template, err := s.cfgCenter.ConfigAPI.SearchDefaultConfigurationTemplate(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("search configuration template failed: %v", err)
	}

	return cvt.From.ConfigCenter.Template(template), nil
}

func (s *svc) CreateTemplate(ctx context.Context, template *ccent.Template, param api.CreateTemplateParam) (*ccent.Template, error) {
	// build template
	req := &ccproto.CreateConfigurationTemplateRequest{
		ClusterType:       cvt.To.ConfigCenter.ClusterType(template.ClusterType),
		ConfigurationType: template.ConfigType,
		TemplateName:      template.Name,
		Data:              make(map[string]*ccproto.ParamItem),
	}
	if template.ID != 0 {
		if !param.AllowAssignID {
			return nil, fmt.Errorf("template id is set but not allow assign tempate id")
		}
		req.TemplateId = pointer.Pointer(int64(template.ID))
	}
	for name, data := range template.Parameters {
		param, err := cvt.To.ConfigCenter.NamedParameter(*data)
		if err != nil {
			return nil, fmt.Errorf("convert item failed: %w", err)
		}
		req.Data[name] = param
	}

	// create in config center
	resp, err := s.cfgCenter.ConfigAPI.CreateConfigurationTemplate(ctx, req)
	if err != nil {
		return nil, err
	}
	newTemplate := cvt.From.ConfigCenter.Template(resp)

	// propagate to sub environments
	if !param.DenyPropagation {
		err := s.propagateTemplates(ctx, newTemplate)
		if err != nil {
			return nil, fmt.Errorf("propagate template failed: %v", err)
		}
	}

	return newTemplate, nil
}

func (s *svc) propagateTemplates(ctx context.Context, template *ccent.Template) error {
	for env, cli := range s.subEnvClients {
		req := cchttpapi.CreateTemplateReq{
			Template:        template,
			AllowAssignID:   true,
			DenyPropagation: true, // not allow continued propagation, let itself to propagate.
		}
		_, err := cli.R().
			SetContext(ctx).
			SetBody(req).
			Post("/api/v1/devops/cloudconfig/template/templates") // MUST match the api
		if err != nil {
			return fmt.Errorf("create template in env %s failed: %v", env, err)
		}
	}

	return nil
}

func (s *svc) DiffTemplate(ctx context.Context, typ ccent.ClusterType, sourceID, targetID uint64) (*api.DiffTemplateResult, error) {
	param := api.GetTemplateParam{
		WithDefaultCases: false,
	}

	srcBuilder := common.NewUnifiedParameterBuilder()
	srcTemplate, err := s.GetTemplate(ctx, typ, sourceID, param)
	if err != nil {
		return nil, fmt.Errorf("get configuration template '%d'  failed: %v", sourceID, err)
	}
	for _, tmplItem := range srcTemplate.Parameters {
		srcBuilder.AddTemplate(tmplItem)
	}

	tarBuilder := common.NewUnifiedParameterBuilder()
	tarTemplate, err := s.GetTemplate(ctx, typ, targetID, param)
	if err != nil {
		return nil, fmt.Errorf("get configuration template '%d'  failed: %v", targetID, err)
	}
	for _, tmplItem := range tarTemplate.Parameters {
		tarBuilder.AddTemplate(tmplItem)
	}

	if tarTemplate.ConfigType != srcTemplate.ConfigType {
		return nil, fmt.Errorf("template '%d' and '%d' are not same config type", sourceID, targetID)
	}

	result := &api.DiffTemplateResult{
		ConfigType:         srcTemplate.ConfigType,
		ModifiedParameters: common.DiffParameters(srcBuilder.Build(), tarBuilder.Build()),
	}

	return result, nil
}

func (s *svc) ListTemplateDefaultCaseBindings(ctx context.Context, typ ccent.ClusterType, param api.ListTemplateDefaultCaseBindingsParam) ([]*ccent.TemplateDefaultCaseBinding, int64, error) {
	req := &ccproto.ListConfigurationTemplateDefaultCaseBindingsRequest{
		ClusterType: cvt.To.ConfigCenter.ClusterType(typ),
		ExactMatch:  param.ExactMatch,
		PageNum:     param.Page,
		PageSize:    param.PerPage,
	}
	if param.ConfigTypeIs != nil {
		req.ConfigurationType = *param.ConfigTypeIs
	}
	if param.DefaultForVersion != nil {
		req.Version = *param.DefaultForVersion
	}
	if param.DefaultForCloudProvider != nil {
		req.CloudProvider = *param.DefaultForCloudProvider
	}
	if param.DefaultForInstanceType != nil {
		req.NodeInstance = *param.DefaultForInstanceType
	}
	if param.DefaultForTenantID != nil {
		req.TenantId = *param.DefaultForTenantID
	}

	resp, err := s.cfgCenter.ConfigAPI.ListConfigurationTemplateDefaultCaseBindings(ctx, req)
	if err != nil {
		return nil, 0, err
	}

	bindings := container.TransformSlice(resp.TemplateDefaultCaseBindings, cvt.From.ConfigCenter.TemplateDefaultCaseBinding)
	return bindings, resp.Total, nil
}

func (s *svc) UpdateTemplateDefaultCaseBinding(ctx context.Context, binding *ccent.TemplateDefaultCaseBinding) error {
	req := &ccproto.UpdateDefaultConfigurationTemplateRequest{
		ClusterType:       cvt.To.ConfigCenter.ClusterType(binding.ClusterType),
		ConfigurationType: binding.DefaultCase.ConfigType,
		Version:           binding.DefaultCase.Version,
		CloudProvider:     binding.DefaultCase.CloudProvider,
		NodeInstance:      binding.DefaultCase.InstanceType,
		TenantId:          binding.DefaultCase.TenantID,
		TemplateId:        int64(binding.TemplateID),
	}

	_, err := s.cfgCenter.ConfigAPI.UpdateDefaultConfigurationTemplate(ctx, req)
	return err
}

func (s *svc) DeleteTemplateDefaultCaseBinding(ctx context.Context, binding *ccent.TemplateDefaultCaseBinding) error {
	req := &ccproto.DeleteDefaultConfigurationTemplateRequest{
		ClusterType:       cvt.To.ConfigCenter.ClusterType(binding.ClusterType),
		ConfigurationType: binding.DefaultCase.ConfigType,
		Version:           binding.DefaultCase.Version,
		CloudProvider:     binding.DefaultCase.CloudProvider,
		NodeInstance:      binding.DefaultCase.InstanceType,
		TenantId:          binding.DefaultCase.TenantID,
		TemplateId:        int64(binding.TemplateID),
	}

	_, err := s.cfgCenter.ConfigAPI.DeleteDefaultConfigurationTemplate(ctx, req)
	return err
}

func (s *svc) BatchUpdateTemplateDefaultCaseBindings(ctx context.Context, bindings []*ccent.TemplateDefaultCaseBinding) error {
	req := &ccproto.BatchUpdateDefaultConfigurationTemplateRequest{}
	for _, binding := range bindings {
		req.Requests = append(req.Requests, &ccproto.UpdateDefaultConfigurationTemplateRequest{
			ClusterType:       cvt.To.ConfigCenter.ClusterType(binding.ClusterType),
			ConfigurationType: binding.DefaultCase.ConfigType,
			Version:           binding.DefaultCase.Version,
			CloudProvider:     binding.DefaultCase.CloudProvider,
			NodeInstance:      binding.DefaultCase.InstanceType,
			TenantId:          binding.DefaultCase.TenantID,
			TemplateId:        int64(binding.TemplateID),
		})
	}
	_, err := s.cfgCenter.ConfigAPI.BatchUpdateDefaultConfigurationTemplate(ctx, req)
	return err
}
