package consul

import (
	"fmt"
	"net/http"
	"strconv"
	"time"

	"git.pbiernat.io/egommerce/go-api-pkg/config"
	consul "github.com/hashicorp/consul/api"
	"github.com/hashicorp/consul/connect"
)

type Service struct {
	Name       string
	Address    string
	appID      string
	domain     string
	pathPrefix string
	port       int
	ttl        time.Duration
	client     *consul.Client
	agent      *consul.Agent
	connect    *connect.Service
	kv         *consul.KV
}

var ErrServiceUnavailable = fmt.Errorf("Service is unavailable")

func NewService(servAddr, id, name, useDomainOverIp, addr, domain, pathPrefix string, appPort int) (*Service, error) {
	s := new(Service)
	s.Name = name
	s.Address = addr
	s.appID = id
	s.domain = domain
	s.pathPrefix = pathPrefix
	s.port = appPort
	s.ttl = time.Second * 10

	if useDomainOverIp == "true" { // FIXME types...
		s.Address = domain
	}

	client, err := consul.NewClient(newClientConfig(servAddr))
	if err != nil {
		return nil, err
	}

	s.client = client
	s.agent = client.Agent()
	s.kv = client.KV()

	return s, nil
}

func newClientConfig(serverAddr string) *consul.Config {
	conf := consul.DefaultConfig()
	conf.Address = serverAddr

	return conf
}

func (s *Service) GetID() string {
	return fmt.Sprintf("%s:%s", s.Name, s.appID)
}

func (s *Service) GetFullAddr() string {
	isTLS := s.port == 443
	proto := "http"
	if isTLS {
		proto = "https"
	}
	return fmt.Sprintf("%s://%s:%d/", proto, s.Address, s.port)
}

func (s *Service) Register() error {
	def := &consul.AgentServiceRegistration{
		ID: s.GetID(),
		// Kind:    consul.ServiceKindConnectProxy,
		Name:    s.Name,
		Address: s.Address,
		Port:    s.port,
		Tags:    s.getTags(),
		// Connect: &consul.AgentServiceConnect{Native: true},
		// Proxy: &consul.AgentServiceConnectProxyConfig{
		// 	DestinationServiceName: s.Name,
		// },
		Check: &consul.AgentServiceCheck{
			TTL:                            s.ttl.String(),
			Status:                         "passing",
			DeregisterCriticalServiceAfter: "10s",
		},
	}

	if err := s.agent.ServiceRegister(def); err != nil {
		return err
	}

	return nil
}
func (s *Service) Unregister() error {
	// s.client.Catalog().Deregister(&consul.CatalogDeregistration{
	// 	Address:   s.Address,
	// 	ServiceID: s.GetID(),
	// }, nil)

	return s.agent.ServiceDeregister(s.GetID())
}

func (s *Service) RegisterHealthChecks() {
	go func() { // startup register
		ticker := time.NewTicker(time.Second * 1)
		for range ticker.C {
			if ok, _ := s.healthCheck(); ok {
				ticker.Stop()
			}
		}
	}()

	go func() { // TTL
		interval := s.ttl - (time.Second * 2) // 2 seconds overhead
		ticker := time.NewTicker(interval)
		for range ticker.C {
			if _, err := s.healthCheck(); err != nil {
				fmt.Printf("HealthCheck endpoint not available #: %v\n", err)
			}
		}
	}()
}

func (s *Service) Connect() (*connect.Service, error) {
	// l := hclog.New(&hclog.LoggerOptions{
	// 	Name:  "consul-registry",
	// 	Level: hclog.Trace,
	// })
	svc, err := connect.NewService(s.Name, s.client)
	s.connect = svc
	cnf := svc.ServerTLSConfig()
	fmt.Printf("CONNECT SERVER:: %s CONFIG:: %v\n", s.Name, cnf)
	for k, c := range cnf.Certificates {
		fmt.Printf("CONNECT CERT %d: %v", k, c)
	}

	return svc, err
}

func (s *Service) KV() *consul.KV {
	return s.kv
}

func (s *Service) healthCheck() (bool, error) {
	alive := func() bool {
		client := &http.Client{}
		healthUrl := fmt.Sprintf("%s%s?name=%s", s.GetFullAddr(), "health", s.Name)
		req, err := http.NewRequest(http.MethodGet, healthUrl, nil)
		if err != nil {
			return false
		}
		req.Header.Set("User-Agent", "service/internal")

		resp, err := client.Do(req)
		if err != nil {
			return false
		}
		defer resp.Body.Close()

		var body []byte
		resp.Body.Read(body)

		return resp.StatusCode == http.StatusOK
	}()

	if alive {
		if err := s.agent.PassTTL("service:"+s.GetID(), "OK"); err != nil {
			fmt.Printf("Failed to pass TTL: %v", err)
			return false, err
		}
		return true, nil
	}

	if err := s.agent.FailTTL("service:"+s.GetID(), ErrServiceUnavailable.Error()); err != nil {
		return false, err
	}
	return false, ErrServiceUnavailable
}

func (s *Service) getTags() []string {
	tags := []string{
		"traefik.enable=true",
		// "traefik.http.middlewares.auth_" + s.Name + ".forwardauth.trustForwardHeader=true",
		// "traefik.http.middlewares.auth_" + s.Name + ".forwardauth.authRequestHeaders=Cookie",
		// "traefik.http.middlewares.auth_" + s.Name + ".forwardauth.authResponseHeaders=Set-Cookie, Server",
		"traefik.http.middlewares.auth_" + s.Name + ".plugin.auth.handlerURL=" + config.GetEnv("AUTH_HANDLER_URL", "http://identity.service.ego.io/api/v1/traefik"),
		"traefik.http.middlewares.requestid_" + s.Name + ".plugin.requestid.headerName=X-Request-ID",
		"traefik.http.middlewares.stripprefix_" + s.Name + ".stripprefix.prefixes=" + s.pathPrefix,
		"traefik.http.routers." + s.Name + ".rule=PathPrefix(`" + s.pathPrefix + "`)",
		// "traefik.http.routers." + s.Name + ".rule=Host(`" + s.domain + "`)",
		"traefik.http.routers." + s.Name + ".entryPoints=https",
		// "traefik.http.routers." + s.Name + ".tls=true",
		"traefik.http.routers." + s.Name + ".service=" + s.Name,
		// "traefik.http.routers." + s.Name + ".middlewares=auth_" + s.Name + ",stripprefix_" + s.Name,
		"traefik.http.routers." + s.Name + ".middlewares=auth_" + s.Name + ",requestid_" + s.Name + ",stripprefix_" + s.Name + "",
		"traefik.http.services." + s.Name + ".loadbalancer.server.scheme=http",
		"traefik.http.services." + s.Name + ".loadbalancer.server.port=" + strconv.Itoa(s.port),
		"traefik.http.services." + s.Name + ".loadbalancer.passhostheader=true",
		"traefik.http.services." + s.Name + ".loadbalancer.healthcheck.interval=1s",
		"traefik.http.services." + s.Name + ".loadbalancer.healthcheck.timeout=1s",
		"traefik.http.services." + s.Name + ".loadbalancer.healthcheck.path=/health",
		"traefik.tls.certificates.certfile=/certs/client.cert",
		"traefik.tls.certificates.keyfile=/certs/client.key",
	}

	return tags
}