package main

import (
	"fmt"
	"log"
	"net"
	"net/url"
	"os"
	"sort"
	"strconv"
	"time"

	"code.justin.tv/common/chitin"
	"code.justin.tv/common/golibs/pkgpath"

	"github.com/hashicorp/consul/api"
	"golang.org/x/net/context"
)

const (
	consulHealthTTL      = 10 * time.Second
	consulHealthInterval = 4 * time.Second
)

func (s *server) registerConsul(ctx context.Context) error {
	cfg := &api.Config{
		HttpClient: chitin.Client(ctx),
	}
	cc, err := api.NewClient(cfg)
	if err != nil {
		return err
	}

	pid := os.Getpid()

	// TODO: service name should reflect the api we expose, not the detail of
	// which binary is providing it
	name, _ := pkgpath.Main()

	port := 0
	if ta, ok := s.sock.Addr().(*net.TCPAddr); ok {
		port = ta.Port
	}

	tags := make(url.Values)
	tags.Set("registration", "auto")
	tags.Set("pid", strconv.Itoa(pid))
	tags.Set("debug", s.debugSock.Addr().String())

	serviceID := fmt.Sprintf("pid=%d", pid)

	err = cc.Agent().ServiceDeregister(serviceID)
	if err != nil {
		return err
	}

	service := &api.AgentServiceRegistration{
		ID:      serviceID,
		Name:    name,
		Tags:    tagSlice(tags),
		Port:    port,
		Address: "",
		Check: &api.AgentServiceCheck{
			TTL: consulHealthTTL.String(),
		},
		Checks: nil,
	}

	err = cc.Agent().ServiceRegister(service)
	if err != nil {
		return err
	}

	go s.keepaliveConsul(ctx, cc, consulHealthInterval, serviceID)

	return nil
}

func (s *server) keepaliveConsul(ctx context.Context, cc *api.Client, period time.Duration, serviceID string) {
	checkID := "service:" + serviceID

	ticker := time.NewTicker(period)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			err := cc.Agent().PassTTL(checkID, "")
			if err != nil {
				log.Printf("consul keepalive err=%q", err)
			}
		}
	}
}

func (s *server) deregisterConsul(ctx context.Context) error {
	// TODO: make this not race against process shutdown

	cfg := &api.Config{
		HttpClient: chitin.Client(ctx),
	}
	cc, err := api.NewClient(cfg)
	if err != nil {
		return err
	}

	pid := os.Getpid()
	serviceID := fmt.Sprintf("pid=%d", pid)

	err = cc.Agent().ServiceDeregister(serviceID)
	if err != nil {
		return err
	}

	return nil
}

func (s *server) handleDeregisterConsul(ctx context.Context, done <-chan struct{}) {
	<-done

	err := s.deregisterConsul(ctx)
	if err != nil {
		log.Printf("deregister consul err=%q", err)
	}
}

func tagSlice(v url.Values) []string {
	keys := make([]string, 0, len(v))
	for k := range v {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	t := make([]string, 0, len(keys))
	for _, k := range keys {
		// TODO: is url.QueryEscape the right escaping to apply here?
		safeKey := url.QueryEscape(k)
		vals := v[k]
		if len(vals) == 0 {
			t = append(t, safeKey)
			continue
		}
		for _, val := range vals {
			t = append(t, safeKey+"="+url.QueryEscape(val))
		}
	}
	return t
}
