package discovery

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client"
	"github.com/nacos-group/nacos-sdk-go/v2/model"
	"github.com/nacos-group/nacos-sdk-go/v2/vo"
	"google.golang.org/grpc/resolver"
	"google.golang.org/grpc/serviceconfig"
)

const name = "service"

// Option is builder option.
type Option func(o *builder)

// WithTimeout with timeout option.
func WithTimeout(timeout time.Duration) Option {
	return func(b *builder) {
		b.timeout = timeout
	}
}

// WithInsecure with isSecure option.
func WithInsecure(insecure bool) Option {
	return func(b *builder) {
		b.insecure = insecure
	}
}

// WithSubset with subset size.
func WithSubset(size int) Option {
	return func(b *builder) {
		b.subsetSize = size
	}
}

// Deprecated: please use PrintDebugLog
// DisableDebugLog disables update instances log.
func DisableDebugLog() Option {
	return func(b *builder) {
		b.debugLog = false
	}
}

// PrintDebugLog print grpc resolver watch service log
func PrintDebugLog(p bool) Option {
	return func(b *builder) {
		b.debugLog = p
	}
}

func ClusterName(name string) Option {
	return func(o *builder) {
		o.clusterName = name
	}
}

func GroupName(name string) Option {
	return func(o *builder) {
		o.groupName = name
	}
}

type builder struct {
	discoverer             naming_client.INamingClient
	clusterName, groupName string
	timeout                time.Duration
	insecure               bool
	subsetSize             int
	debugLog               bool
}

// NewBuilder creates a builder which is used to factory registry resolvers.
func NewBuilder(d naming_client.INamingClient, opts ...Option) resolver.Builder {
	b := &builder{
		discoverer: d,
		timeout:    time.Second * 5,
		insecure:   false,
		debugLog:   true,
		subsetSize: 25,
	}
	for _, o := range opts {
		o(b)
	}
	return b
}

func (b *builder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) {
	ctx, cancel := context.WithCancel(context.Background())
	serviceName := strings.TrimPrefix(target.URL.Path, "/")

	go b.discoverer.Subscribe(&vo.SubscribeParam{
		ServiceName: serviceName,
		Clusters:    []string{b.clusterName},
		GroupName:   b.groupName,
		SubscribeCallback: func(services []model.Instance, err error) {
			var addresses []resolver.Address
			for _, instance := range services {
				if instance.Healthy && instance.Enable {
					addresses = append(addresses, resolver.Address{
						Addr:       fmt.Sprintf(`%s:%d`, instance.Ip, instance.Port),
						ServerName: serviceName,
					})
				}
			}
			if len(addresses) > 0 {
				_ = cc.UpdateState(resolver.State{Addresses: addresses})
			} else {
				_ = cc.UpdateState(resolver.State{ServiceConfig: &serviceconfig.ParseResult{Err: errors.New(`service not found`)}})
			}
		},
	})
	var err error
	select {
	case <-time.After(b.timeout):
		err = errors.New("discovery create watcher overtime")
	default:

	}
	if err != nil {
		cancel()
		return nil, err
	}

	r := &discoveryResolver{
		w:           b.discoverer,
		cc:          cc,
		ctx:         ctx,
		clusterName: b.clusterName,
		serviceName: serviceName,
		groupName:   b.groupName,
		cancel:      cancel,
	}
	go r.watch()
	return r, nil
}

// Scheme return scheme of discovery
func (*builder) Scheme() string {
	return name
}
