package common

import (
	"fmt"
	"maps"
	"reflect"
	"strings"

	"github.com/samber/lo"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
)

func Elem(t reflect.Type) reflect.Type {
	if t.Kind() != reflect.Pointer && t.Kind() != reflect.Array && t.Kind() != reflect.Slice {
		return t
	}

	t = t.Elem()

	if t.Kind() == reflect.Pointer {
		return t.Elem()
	}

	return t
}

// FindReferences find all indirectly referenced tables
func FindReferences(model any) ([]string, error) {
	rt := Elem(reflect.TypeOf(model))

	if rt.Kind() != reflect.Struct {
		return nil, fmt.Errorf("only support struct type %v", rt)
	}

	var result []string
	if err := findReferences(rt, "", &result, make(map[string]bool)); err != nil {
		return nil, err
	}

	return lo.Filter(result, func(item string, index int) bool {
		arr := strings.Split(item, ".")
		return len(arr) > 1 && len(arr) < 4
	}), nil
}

func findReferences(rt reflect.Type, prefix string, result *[]string, parentVisited map[string]bool) error {
	branchVisited := make(map[string]bool)
	maps.Copy(branchVisited, parentVisited)
	if branchVisited[rt.Name()] {
		return nil
	}
	branchVisited[rt.Name()] = true

	for i := 0; i < rt.NumField(); i++ {
		fieldType := Elem(rt.Field(i).Type)
		fieldTypeName := fieldType.Name()

		if fieldType.Kind() == reflect.Struct {
			if fieldTypeName != "Time" && fieldTypeName != "DeletedAt" && fieldTypeName != "Model" {
				var newPrefix string
				if prefix == "" {
					newPrefix = rt.Field(i).Name
				} else {
					newPrefix = fmt.Sprintf("%s.%s", prefix, rt.Field(i).Name)
				}

				*result = append(*result, newPrefix)
				if err := findReferences(fieldType, newPrefix, result, branchVisited); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func SetOrderByIntoTx(tx *gorm.DB, sorters []*globaldedicatedv1.Sorter) *gorm.DB {
	for _, sorter := range sorters {
		desc := true
		if sorter.Sort == globaldedicatedv1.Sorter_ASC {
			desc = false
		}
		tx = tx.Order(clause.OrderByColumn{Column: clause.Column{Name: sorter.OrderBy}, Desc: desc})
	}

	return tx
}

func SetOffsetAndLimitIntoTx(tx *gorm.DB, pager *globaldedicatedv1.Pager) *gorm.DB {
	if pager == nil {
		return tx
	}

	offset := (pager.Page - 1) * pager.PageSize
	limit := pager.PageSize

	return tx.Offset(int(offset)).Limit(int(limit))
}

func GetSpuName(spuKey string) string {
	return strings.Split(spuKey, ":")[0]
}
