package utils

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/labstack/gommon/random"

	"github.com/pingcap/test-infra/common/model"
)

const (
	EmptyJSONArray = "[]"
)

// ToJSON returns `defaultWhenZero` when `src` is the zero value for its type.
func ToJSON(src any, defaultWhenZero ...string) []byte {
	if src == nil {
		return nil
	}

	if len(defaultWhenZero) > 0 {
		v := reflect.ValueOf(src)
		if v.Kind() != reflect.Invalid && v.IsZero() {
			return []byte(defaultWhenZero[0])
		}
	}

	res, err := json.Marshal(src)
	if err != nil {
		panic(err)
	}
	return res
}

const defaultMaxItem = 100

func ParseCSV(s string, maxItems ...int) ([]string, error) {
	if s == "" {
		return []string{}, nil
	}

	maxItem := defaultMaxItem
	if len(maxItems) != 0 {
		maxItem = maxItems[0]
	}

	res := strings.Split(s, ",")
	if len(res) > maxItem {
		return nil, errors.New("item count should <= " + strconv.Itoa(maxItem))
	}

	return res, nil
}

func ParseCSVID(s string, maxItems ...int) ([]model.ID, error) {
	if s == "" {
		return []model.ID{}, nil
	}

	ids, err := ParseCSV(s, maxItems...)
	if err != nil {
		return nil, err
	}

	res := make([]model.ID, len(ids))
	for i, id := range ids {
		id, err := ParseID(id)
		if err != nil {
			return nil, err
		}
		res[i] = model.ID(id)
	}
	return res, nil
}

// `s` must be in RFC3339Nano format.
func ParseTime(s string) (time.Time, error) {
	return time.Parse(time.RFC3339Nano, s)
}

func ParseID(s string) (model.ID, error) {
	id, err := strconv.ParseUint(s, 10, 64)
	if err != nil {
		return 0, fmt.Errorf("should be unsigned integer, but got %s", s)
	}
	return model.ID(id), nil
}

func FormatCSVID(ids []model.ID) string {
	if len(ids) == 0 {
		return ""
	}

	s := strings.Builder{}
	for i, id := range ids {
		if i > 0 {
			s.WriteString(",")
		}
		s.WriteString(id.String())
	}
	return s.String()
}

func UniqSlice[T comparable](xs []T) []T {
	if len(xs) <= 1 {
		return xs
	}

	set := ToSet(xs)
	res := make([]T, len(set))
	i := 0
	for s := range set {
		res[i] = s
		i++
	}
	return res
}

func SlicesEqual[T comparable](a, b []T) bool {
	if len(a) != len(b) {
		return false
	}

	for i, v := range a {
		if v != b[i] {
			return false
		}
	}
	return true
}

func ToSet[T comparable](xs []T) map[T]struct{} {
	m := make(map[T]struct{}, len(xs))
	for _, x := range xs {
		m[x] = struct{}{}
	}
	return m
}

func ToMap[K comparable, V any](xs []K) map[K]V {
	m := make(map[K]V, len(xs))
	for _, x := range xs {
		m[x] = *new(V)
	}
	return m
}

func NewReqID() string {
	return random.String(8)
}
