package configcenter

import (
	"context"
	"fmt"

	"go.uber.org/dig"

	"github.com/go-logr/logr"
	"github.com/tidbcloud/devops/entity"
	"github.com/tidbcloud/devops/pkg/infra/config"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"
	"github.com/tidbcloud/devops/pkg/service"
	"github.com/tidbcloud/devops/pkg/service/configcenter/repo"
	"github.com/tidbcloud/pkgv2/db"
)

var _ ConfigCenterService = &svc{}

type ServiceParam struct {
	dig.In

	Conf       *config.RootConfig
	Logger     logr.Logger
	DBProvider db.TransProvider
}

type ServiceResult struct {
	dig.Out

	API     ConfigCenterServiceAPI
	Service service.Service `group:"service"`
}

type svc struct {
	*service.BaseService

	dbp        db.TransProvider
	cfgctrRepo repo.ConfigCenterRepo
}

func NewService(p ServiceParam) (ServiceResult, error) {
	base := service.NewBaseService("ConfigCenterService")
	s := &svc{
		BaseService: base,
		dbp:         p.DBProvider,
		cfgctrRepo:  repo.NewConfigCenterReop(p.DBProvider),
	}
	return ServiceResult{API: s, Service: s}, nil
}

func (s *svc) GetApplyPolicy(ctx context.Context, param GetApplyPolicyReq) (entity.ApplyStrategy, error) {
	exist, err := s.cfgctrRepo.GetConfigWithNameNVersion(ctx, param.ParamName, param.Version)
	if err == nil {
		return *exist.ApplyPolicy, nil
	}
	if errs.IsNotFoundInDB(err) {
		return entity.Restart, nil
	}
	s.Logger(ctx).Error(err, "fail to find apply policy", "param name", param.ParamName, "param version", param.Version)
	return entity.Restart, err
}

func (s *svc) ListAttributes(ctx context.Context, param ListAttributesReq) ([]*entity.ConfigAttributes, error) {
	ops := make([]repoopt.Option, 0)
	if param.Version != `` {
		version, err := versionToInt64(param.Version)
		if err != nil {
			return nil, fmt.Errorf("cannot parse the version string, please input the version like vXX.XX.XX, %w", err)
		}
		ops = append(ops, repo.WithVersion(version))
	}
	if param.ParamName != `` {
		ops = append(ops, repo.WithParamName(param.ParamName))
	}
	if param.Component != `` {
		ops = append(ops, repo.WithComponent(param.Component))
	}
	attributes, err := s.cfgctrRepo.ListAttributes(ctx, ops...)
	if err != nil {
		return nil, err
	}
	return attributes, nil
}

func (s *svc) AddAttribute(ctx context.Context, param AddAttributeReq) error {
	err := s.dbp.Transaction(ctx, func(ctx context.Context) error {
		p := entity.ConfigAttributes(param)
		return s.cfgctrRepo.AddParam(ctx, &p)
	})
	if err != nil {
		s.Logger(ctx).Error(err, "error when write apply policy to db")
		return err
	}
	s.Logger(ctx).V(4).Info("successfuly write apply policy to db", "param name", param.ParamName, "version", param.Version)
	return nil
}

func (s *svc) CopyFromVersion(ctx context.Context, param CopyFromVersionReq) error {
	fromVersionInt64, err := versionToInt64(param.FromVersion)
	if err != nil {
		return err
	}
	toVersionInt64, err := versionToInt64(param.ToVersion)
	if err != nil {
		return err
	}

	tos, err := s.cfgctrRepo.ListAttributes(ctx, repo.WithVersion(toVersionInt64))
	if err != nil && !errs.IsNotFoundInDB(err) {
		return err
	}
	if errs.IsNotFoundInDB(err) || len(tos) == 0 {
		// expected, copy.
		err = s.dbp.Transaction(ctx, func(ctx context.Context) error {
			oldAttributes, err := s.cfgctrRepo.ListAttributes(ctx, repo.WithVersion(fromVersionInt64))
			if err != nil {
				return err
			}
			toBeAdded := make([]*entity.ConfigAttributes, 0)
			for _, v := range oldAttributes {
				toBeAdded = append(toBeAdded,
					&entity.ConfigAttributes{
						Version:     toVersionInt64,
						ParamName:   v.ParamName,
						ApplyPolicy: v.ApplyPolicy})
			}
			return s.cfgctrRepo.CreateAttributes(ctx, toBeAdded)
		})
		if err != nil {
			return err
		}
		return nil
	}
	return fmt.Errorf("version %s already exists", param.ToVersion)
}

func (s *svc) Modify(ctx context.Context, param ModifyReq) error {
	return s.dbp.Transaction(ctx, func(ctx context.Context) error {
		return s.cfgctrRepo.PatchAttribute(ctx, param.ConfigAttribute.ParamName, param.ConfigAttribute.Version,
			func(current *entity.ConfigAttributes) error {
				current.ApplyPolicy = param.ConfigAttribute.ApplyPolicy
				return nil
			})
	})
}
