package common

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/pingcap/log"
	"github.com/pkg/errors"
	"go.uber.org/zap"
)

const (
	ConfigPlacementRules = "/pd/api/v1/config/rules"
	ConfigPlacementRule  = "/pd/api/v1/config/rule"
)

type PeerRoleType string
type ConstraintOp string
type Constraint struct {
	Key    string       `json:"key,omitempty"`
	Op     ConstraintOp `json:"op,omitempty"`
	Values []string     `json:"values,omitempty"`
}
type Constraints []Constraint

type PlacementRule struct {
	GroupID     string       `json:"group_id"`
	ID          string       `json:"id"`
	Index       int          `json:"index,omitempty"`
	Override    bool         `json:"override,omitempty"`
	StartKeyHex string       `json:"start_key"`
	EndKeyHex   string       `json:"end_key"`
	Role        PeerRoleType `json:"role"`
	Count       int          `json:"count"`
	Constraints Constraints  `json:"label_constraints,omitempty"`
}

type PlacementRules []*PlacementRule

func (r *PlacementRules) WriteTo(f *os.File) error {
	regionRuleBytes, err := json.Marshal(r)
	if err != nil {
		return err
	}
	return WriteFile(f, regionRuleBytes)
}

func (r *PlacementRules) ReadFrom(f *os.File) error {
	placementRuleBytes, err := io.ReadAll(f)
	if err != nil {
		return err
	}

	return json.Unmarshal(placementRuleBytes, r)
}

const PlacementRuleSliceLen = 2

func GetKsIdFromPlacementRule(placementRuleID string) (string, error) {
	// rule.ID prefixed with `fmt.Sprintf("keyspace-%d", keyspaceId)`
	// example: keyspace-1183-table-91-r
	arr := strings.Split(placementRuleID, "-")
	if len(arr) < PlacementRuleSliceLen {
		return "", errors.Errorf("Failed to parse keyspaceId from placementRule:%s", placementRuleID)
	}
	return arr[1], nil
}

func CachePlacementRules(f *os.File) (map[string]PlacementRules, error) {
	var rules PlacementRules

	err := rules.ReadFrom(f)
	if err != nil {
		return nil, err
	}
	return PlacementRules2Map(rules)
}

func PlacementRules2Map(rules PlacementRules) (map[string]PlacementRules, error) {
	rulesMap := make(map[string]PlacementRules)
	for _, rule := range rules {
		isKeyspacePlacementRule := strings.HasPrefix(rule.ID, "keyspace")
		if isKeyspacePlacementRule {
			keyspaceId, err := GetKsIdFromPlacementRule(rule.ID)
			if err != nil {
				return nil, err
			}
			if len(rulesMap[keyspaceId]) == 0 {
				rulesMap[keyspaceId] = []*PlacementRule{}
			}
			rulesMap[keyspaceId] = append(rulesMap[keyspaceId], rule)
		}
	}
	return rulesMap, nil
}

func GetAllPlacementRules(ctx context.Context, pdctx *PdContext) (PlacementRules, error) {
	res, err := DoRequestForNoNeedConfirm(ctx, pdctx, ConfigPlacementRules, "GET", nil, true)
	if err != nil {
		return nil, err
	}

	var rules PlacementRules
	err = json.Unmarshal(res, &rules)
	if err != nil {
		return nil, err
	}

	count := 0
	for _, rule := range rules {
		if strings.HasPrefix(rule.ID, "keyspace") {
			count++
		}
	}
	log.Info("total keyspace placement rules", zap.Int("count", count))
	return rules, nil
}

func DeletePlacementRule(ctx context.Context, pdctx *PdContext,
	rule *PlacementRule, isRun bool, isSkipConfirm bool) error {
	uri := fmt.Sprintf("%s/%s/%s", ConfigPlacementRule, rule.GroupID, rule.ID)
	res, err := DoRequestForExecute(ctx, pdctx, uri, "DELETE", nil, isRun, isSkipConfirm)
	if err != nil {
		return err
	}
	if res == nil && isRun {
		return fmt.Errorf("returns error in DeletePlacementRule")
	}
	return nil
}
