package naming

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"net/url"
	"os"
	"sort"

	"github.com/go-kratos/kratos/pkg/conf/env"
	"github.com/go-kratos/kratos/pkg/log"

	"github.com/dgryski/go-farm"
)

// BuildOptions build options.
type BuildOptions struct {
	Filter     func(map[string][]*Instance) map[string][]*Instance
	Subset     func([]*Instance, int) []*Instance
	SubsetSize int
	ClientZone string
	Scheduler  func(*InstancesInfo) []*Instance
}

// BuildOpt build option interface.
type BuildOpt interface {
	Apply(*BuildOptions)
}

type funcOpt struct {
	f func(*BuildOptions)
}

func (f *funcOpt) Apply(opt *BuildOptions) {
	f.f(opt)
}

// Filter filter option.
func Filter(schema string, clusters map[string]struct{}) BuildOpt {
	return &funcOpt{f: func(opt *BuildOptions) {
		opt.Filter = func(inss map[string][]*Instance) map[string][]*Instance {
			newInss := make(map[string][]*Instance)
			for zone := range inss {
				var instances []*Instance
				for _, ins := range inss[zone] {
					//如果r.clusters的长度大于0说明需要进行集群选择
					if len(clusters) > 0 {
						if _, ok := clusters[ins.Metadata[MetaCluster]]; !ok {
							continue
						}
					}
					var addr string
					for _, a := range ins.Addrs {
						u, err := url.Parse(a)
						if err == nil && u.Scheme == schema {
							addr = u.Host
						}
					}
					if addr == "" {
						fmt.Fprintf(os.Stderr, "resolver: app(%s,%s) no valid grpc address(%v) found!", ins.AppID, ins.Hostname, ins.Addrs)
						log.Warn("resolver: invalid rpc address(%s,%s,%v) found!", ins.AppID, ins.Hostname, ins.Addrs)
						continue
					}
					instances = append(instances, ins)
				}
				newInss[zone] = instances
			}
			return newInss
		}
	}}
}

func defulatSubset(inss []*Instance, size int) []*Instance {
	backends := inss
	if len(backends) <= size {
		return backends
	}
	clientID := env.Hostname
	sort.Slice(backends, func(i, j int) bool {
		return backends[i].Hostname < backends[j].Hostname
	})
	count := len(backends) / size
	// hash得到ID
	id := farm.Fingerprint64([]byte(clientID))
	// 获得rand轮数
	round := int64(id / uint64(count))

	s := rand.NewSource(round)
	ra := rand.New(s)
	//  根据source洗牌
	ra.Shuffle(len(backends), func(i, j int) {
		backends[i], backends[j] = backends[j], backends[i]
	})
	start := (id % uint64(count)) * uint64(size)
	return backends[int(start) : int(start)+size]
}

// Subset Subset option.
func Subset(defaultSize int) BuildOpt {
	return &funcOpt{f: func(opt *BuildOptions) {
		opt.SubsetSize = defaultSize
		opt.Subset = defulatSubset
	}}
}

// ScheduleNode ScheduleNode option.
func ScheduleNode(clientZone string) BuildOpt {
	return &funcOpt{f: func(opt *BuildOptions) {
		opt.ClientZone = clientZone
		opt.Scheduler = func(app *InstancesInfo) (instances []*Instance) {
			type Zone struct {
				inss   []*Instance
				weight int64
				name   string
				score  float64
			}
			var zones []*Zone

			if app.Scheduler != nil {
				si, err := json.Marshal(app.Scheduler)
				if err == nil {
					log.Info("schedule info: %s", string(si))
				}
				if strategy, ok := app.Scheduler.Clients[clientZone]; ok {
					var min *Zone
					for name, zone := range strategy.Zones {
						inss := app.Instances[name]
						if len(inss) == 0 {
							continue
						}
						z := &Zone{
							inss:   inss,
							weight: zone.Weight,
							name:   name,
							score:  float64(len(inss)) / float64(zone.Weight),
						}
						if min == nil || z.score < min.score {
							min = z
						}
						zones = append(zones, z)
					}
					if opt.SubsetSize != 0 && len(min.inss) > opt.SubsetSize {
						min.score = float64(opt.SubsetSize) / float64(min.weight)
					}
					for _, z := range zones {
						nums := int(min.score * float64(z.weight))
						if nums == 0 {
							nums = 1
						}
						if nums < len(z.inss) {
							if opt.Subset != nil {
								z.inss = opt.Subset(z.inss, nums)
							} else {
								z.inss = defulatSubset(z.inss, nums)
							}
						}
					}
				}
			}
			for _, zone := range zones {
				instances = append(instances, zone.inss...)
			}
			//如果没有拿到节点，则选择直接获取
			if len(instances) == 0 {
				instances = app.Instances[clientZone]
				if len(instances) == 0 {
					for _, value := range app.Instances {
						instances = append(instances, value...)
					}
				}
			}
			return
		}
	}}
}
