package grpc_service

import (
	"context"
	"fmt"

	"github.com/tidbcloud/config-center/internal/db"
	"github.com/tidbcloud/config-center/internal/service"
	"github.com/tidbcloud/config-center/pkg/utils"
	pb "github.com/tidbcloud/config-center/proto/config_center"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func pb2Row(v *pb.ConfigurationDictItem) *db.ConfigDictRow {
	item := &db.ConfigDictRow{
		ClusterType:   v.GetClusterType().String(),
		ConfigType:    v.GetConfigurationType(),
		Version:       v.GetVersion(),
		ParamKey:      v.GetParamKey(),
		DefaultValue:  db.NewAnyValue(v.GetDefaultValue().AsInterface()),
		Type:          pb.ConfigParamType_name[int32(v.GetType())],
		HotReloadable: v.HotReloadable,
		Description:   v.Description,
		NeedTemplate:  v.NeedTemplate,
		Validation:    utils.NilIfEmpty(v.Validation),
	}
	return item
}

func batchInsertConfigurationDictItems(d db.DB, req *pb.BatchCreateConfigurationDictRequest) error {
	rows := make([]*db.ConfigDictRow, 0)
	for _, v := range req.ConfigurationDictItems {
		item := pb2Row(v)
		rows = append(rows, item)
	}
	return d.InsertConfigDicts(rows)
}

func (c *ConfigCenterService) BatchCreateConfigurationDictItems(ctx context.Context, req *pb.BatchCreateConfigurationDictRequest) (*pb.BatchCreateConfigurationDictResponse, error) {
	// todo: fill default values?
	return &pb.BatchCreateConfigurationDictResponse{
		ConfigurationDictItems: req.ConfigurationDictItems,
	}, batchInsertConfigurationDictItems(c.DB, req)
}

func (c *ConfigCenterService) GetConfigurationDictItem(ctx context.Context, req *pb.GetConfigurationDictItemRequest) (*pb.ConfigurationDictItem, error) {
	if req.ClusterType == pb.ClusterType_CLUSTER_TYPE_UNSPECIFIED {
		// compatibility for old clients
		req.ClusterType = pb.ClusterType_DEDICATED_TIDB
	}

	return service.GetConfigurationDictItem(c.DB, req)
}

func (c *ConfigCenterService) ListConfigurationDictItems(ctx context.Context, req *pb.ListConfigurationDictItemsRequest) (*pb.ListConfigurationDictItemsResponse, error) {
	clusterType := req.GetClusterType()
	if clusterType == pb.ClusterType_CLUSTER_TYPE_UNSPECIFIED {
		// compatibility for old clients
		clusterType = pb.ClusterType_DEDICATED_TIDB
	}

	version := req.Version
	if req.GetMatchPartialVersion() && version != nil {
		v, err := service.MatchPartialVersionFromDict(c.DB, clusterType.DictSource().String(), req.GetConfigurationType(), *version)
		if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
			return &pb.ListConfigurationDictItemsResponse{}, nil
		}
		if err != nil {
			return nil, err
		}
		version = &v
	}
	opt := &db.ListConfigDictOption{
		ClusterType:  clusterType.DictSource().String(),
		ConfigType:   req.ConfigurationType,
		Version:      version,
		ParamKeyIn:   req.ParamKeyIn,
		ParamKeyLike: req.ParamKeyLike,
		Page:         req.Page,
		PageSize:     req.PageSize,
	}
	items, err := c.DB.ListConfigDict(opt)
	if err != nil {
		return nil, err
	}
	count, err := c.DB.GetTotalParamNum(opt)
	if err != nil {
		return nil, err
	}
	resp := &pb.ListConfigurationDictItemsResponse{
		ConfigurationDictItems: make([]*pb.ConfigurationDictItem, 0),
		Current:                req.Page,
		Total:                  count}
	for _, item := range items {
		pb, err := service.Row2Pb(item)
		if err != nil {
			return nil, err
		}
		pb.ClusterType = clusterType
		resp.ConfigurationDictItems = append(resp.ConfigurationDictItems, pb)
	}
	return resp, nil
}

func (c *ConfigCenterService) ListConfigurationDictVersions(ctx context.Context, req *pb.ListConfigurationDictVersionsRequest) (*pb.ListConfigurationDictVersionsResponse, error) {
	if req.ClusterType == pb.ClusterType_CLUSTER_TYPE_UNSPECIFIED {
		// compatibility for old clients
		req.ClusterType = pb.ClusterType_DEDICATED_TIDB
	}
	return service.ListConfigurationDictVersions(c.DB, req)
}

func (c *ConfigCenterService) UpdateConfigurationDictItem(ctx context.Context, req *pb.UpdateConfigurationDictItemReq) (*pb.ConfigurationDictItem, error) {
	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	clusterType := req.GetClusterType()
	if clusterType == pb.ClusterType_CLUSTER_TYPE_UNSPECIFIED {
		// compatibility for old clients
		clusterType = pb.ClusterType_DEDICATED_TIDB
	}
	if clusterType.DictSource() != clusterType {
		return nil, status.Errorf(codes.InvalidArgument, "dict for %s is mirrored from %s, please edit %s instead", clusterType, clusterType.DictSource(), clusterType.DictSource())
	}
	oldDict, err := tx.GetConfigDict(clusterType.String(), req.ConfigurationType, req.ParamKey, req.Version)
	if err != nil {
		return nil, err
	}
	if oldDict == nil {
		return nil, fmt.Errorf("the required updated item does not exist")
	}
	if req.DefaultValue != nil {
		oldDict.DefaultValue = db.NewAnyValue(req.GetDefaultValue().AsInterface())
	}
	if req.HotReloadable != nil {
		oldDict.HotReloadable = req.HotReloadable
	}
	if req.Validation != nil {
		oldDict.Validation = utils.NilIfEmpty(*req.Validation) // "" -> nil
	}
	if req.Description != nil {
		oldDict.Description = req.Description
	}
	if req.RiskLevel != nil {
		oldDict.RiskLevel = *req.RiskLevel
	}
	if req.UpdatedBy == "" {
		oldDict.UpdatedBy = "unknown"
	}

	err = tx.UpdateConfigDict(oldDict)
	if err != nil {
		return nil, err
	}

	newDict, err := service.GetConfigurationDictItem(tx, &pb.GetConfigurationDictItemRequest{
		ClusterType:       clusterType,
		ConfigurationType: req.ConfigurationType,
		ParamKey:          req.ParamKey,
		Version:           req.Version,
	})
	if err != nil {
		return nil, err
	}
	return newDict, tx.Commit()
}

/*
not used anymore
func (c *ConfigCenterService) CopyConfigurationDictFromOldVersion(ctx context.Context, req *pb.CopyConfigurationDictFromOldVersionRequest) (*emptypb.Empty, error) {
	return &emptypb.Empty{}, copyFromOldVersion(c.DB, req.FromVersion, req.ToVersion)
}

func copyFromOldVersion(d db.DB, fromVersion string, toVersion string) error {
	tx, err := d.Begin()
	if err != nil {
		return err
	}
	news, err := tx.ListConfigDict(&db.ListConfigDictOption{Version: &toVersion})
	if err == db.ErrNotFound || len(news) == 0 {
		olds, err := tx.ListConfigDict(&db.ListConfigDictOption{
			Version: &fromVersion,
		})
		if err != nil {
			tx.Rollback()
			return err
		}
		for _, v := range olds {
			v.Version = toVersion
		}
		err = tx.InsertConfigDicts(olds)
		if err != nil {
			tx.Rollback()
			return err
		}
		return tx.Commit()
	} else if err != nil {
		tx.Rollback()
		return err
	}
	tx.Rollback()
	return fmt.Errorf("some paramter already exist")
}
*/

func (c *ConfigCenterService) PurgeDictItems(ctx context.Context, req *pb.PurgeDictItemsRequest) (*pb.PurgeDictItemsResponse, error) {
	if req.GetForce() != true {
		count, err := c.DB.GetTotalParamNum(&db.ListConfigDictOption{
			ClusterType: req.GetClusterType().String(),
			ConfigType:  &req.ConfigType,
			Version:     req.Version,
		})
		if err != nil {
			return nil, err
		}
		return &pb.PurgeDictItemsResponse{
			PurgeCount: count,
		}, nil
	} else {
		count, err := c.DB.DeleteDictItems(req.GetClusterType().String(), req.GetConfigType(), req.Version)
		if err != nil {
			return nil, err
		}
		return &pb.PurgeDictItemsResponse{
			PurgeCount: count,
		}, nil
	}
}
