package registry

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"git.nspix.com/golang/micro/helper/httpclient"
	"git.nspix.com/golang/micro/helper/utils"
	"io"
	"io/ioutil"
	"net/http"
	"time"
)

const (
	DefaultDiscoveryUrl = "https://discovery.nspix.com"
)

type (
	Discovery struct {
		baseUrl string
		Timeout time.Duration
	}

	discoveryResponse struct {
		Code    int             `json:"errno"`
		Message string          `json:"errmsg"`
		Data    json.RawMessage `json:"result"`
	}
)

func (r *Discovery) sendRequest(ctx context.Context, method, path string, body io.Reader, data interface{}) (err error) {
	var (
		req *http.Request
		res *http.Response
	)
	if req, err = http.NewRequest(method, r.baseUrl+path, body); err != nil {
		return
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	if res, err = httpclient.Do(ctx, req); err != nil {
		return
	}
	defer func() {
		_ = res.Body.Close()
	}()
	rv := &discoveryResponse{}
	if err = json.NewDecoder(res.Body).Decode(rv); err == nil {
		if rv.Code == 0 {
			if data != nil {
				err = json.Unmarshal(rv.Data, data)
			}
		} else {
			err = errors.New(rv.Message)
		}
	}
	return
}

func (r *Discovery) Register(ctx context.Context, instance *ServiceNode) (err error) {
	var (
		buf []byte
	)
	if buf, err = json.Marshal(instance); err != nil {
		return
	}
	if ctx == nil{
		ctx = context.Background()
	}
	cc, cancelFunc := context.WithTimeout(ctx, r.Timeout)
	defer func() {
		cancelFunc()
	}()
	return r.sendRequest(cc, "POST", "/register", bytes.NewReader(buf), nil)
}

func (r *Discovery) Deregister(ctx context.Context, instance *ServiceNode) (err error) {
	cc, cancelFunc := context.WithTimeout(ctx, r.Timeout)
	if ctx == nil{
		ctx = context.Background()
	}
	defer func() {
		cancelFunc()
	}()
	return r.sendRequest(cc, "DELETE", "/deregister/"+instance.ID, nil, nil)
}

func (r *Discovery) Get(ctx context.Context, name string) (instances []*ServiceNode, err error) {
	instances = make([]*ServiceNode, 0)
	if ctx == nil{
		ctx = context.Background()
	}
	cc, cancelFunc := context.WithTimeout(ctx, r.Timeout)
	defer func() {
		cancelFunc()
	}()
	err = r.sendRequest(cc, "GET", "/service?name="+name, nil, &instances)
	return
}

func (r *Discovery) Fetch(ctx context.Context) (instances []*ServiceNode, err error) {
	instances = make([]*ServiceNode, 0)
	if ctx == nil{
		ctx = context.Background()
	}
	cc, cancelFunc := context.WithTimeout(ctx, r.Timeout)
	defer func() {
		cancelFunc()
	}()
	err = r.sendRequest(cc, "GET", "/services", nil, &instances)
	return
}

func NewDiscovery(uri string) *Discovery {
	if uri == "" {
		//兼容k8s的情况
		namespaceFile := "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
		if utils.FileExists(namespaceFile) {
			if buf, err := ioutil.ReadFile(namespaceFile); err == nil {
				uri = fmt.Sprintf("http://discovery.%s.svc.cluster.local", string(buf))
			}
		}
	}
	if uri == "" {
		uri = DefaultDiscoveryUrl
	}
	return &Discovery{
		baseUrl: uri,
		Timeout: time.Second * 10,
	}
}
