package tracing

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"sync"

	openzipkin "github.com/openzipkin/zipkin-go"
	zipkinHTTP "github.com/openzipkin/zipkin-go/reporter/http"
	"go.opencensus.io/exporter/zipkin"
	"go.opencensus.io/plugin/ochttp"
	"go.opencensus.io/trace"
)

var (
	// Enabled reports whether tracing is globally enabled or not.
	Enabled bool

	// The active tracing configuration, if Enabled is true.
	config tracingConfig

	initOnce sync.Once
)

const globalTracingConfigPath = "/etc/tracing/client.conf"

type tracingConfig struct {
	ReportURL string `json:"report_url"`
}

// Read the global tracing configuration file. Its location is
// hardcoded, but it can be overriden using the TRACING_CONFIG
// environment variable.
func readTracingConfig() error {
	// Read and decode configuration.
	cfgPath := globalTracingConfigPath
	if s := os.Getenv("TRACING_CONFIG"); s != "" {
		cfgPath = s
	}
	data, err := ioutil.ReadFile(cfgPath)
	if err != nil {
		return err
	}

	if err := json.Unmarshal(data, &config); err != nil {
		log.Printf("warning: error in tracing configuration: %v, tracing disabled", err)
		return err
	}

	if config.ReportURL == "" {
		log.Printf("warning: tracing configuration contains no report_url, tracing disabled")
		return errors.New("no report_url")
	}

	return nil
}

// Compute the service name for Zipkin: this is usually the program
// name (without path), but it can be overriden by the TRACING_SERVICE
// environment variable.
func getServiceName() string {
	if s := os.Getenv("TRACING_SERVICE"); s != "" {
		return s
	}
	return filepath.Base(os.Args[0])
}

// Initialize tracing. Tracing will be enabled if the system-wide
// tracing configuration file is present and valid. Explicitly set
// TRACING_ENABLE=0 in the environment to disable tracing.
//
// We need to check the configuration as soon as possible, because
// it's likely that client transports are created before HTTP servers,
// and we need to wrap them with opencensus at creation time.
func init() {
	// Kill switch from environment.
	if s := os.Getenv("TRACING_ENABLE"); s == "0" {
		return
	}

	if err := readTracingConfig(); err != nil {
		return
	}

	Enabled = true
}

func initTracing(endpointAddr string) {
	initOnce.Do(func() {
		localEndpoint, err := openzipkin.NewEndpoint(getServiceName(), endpointAddr)
		if err != nil {
			log.Printf("warning: error creating tracing endpoint: %v, tracing disabled", err)
			return
		}

		reporter := zipkinHTTP.NewReporter(config.ReportURL)
		ze := zipkin.NewExporter(reporter, localEndpoint)

		trace.RegisterExporter(ze)
		trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})

		log.Printf("tracing enabled (report_url %s)", config.ReportURL)

		Enabled = true
	})
}

// WrapTransport optionally wraps a http.RoundTripper with OpenCensus
// tracing functionality, if it is globally enabled.
func WrapTransport(t http.RoundTripper) http.RoundTripper {
	if Enabled {
		t = &ochttp.Transport{Base: t}
	}
	return t
}

// WrapHandler wraps a http.Handler with OpenCensus tracing
// functionality, if globally enabled.
func WrapHandler(h http.Handler, endpointAddr string) http.Handler {
	if Enabled {
		initTracing(endpointAddr)
		h = &ochttp.Handler{Handler: h}
	}
	return h
}