/*
Package config enables packages to define configuration keys with defaults. These keys are read in during application start from either command-line flags or environment variables.

Since the configuration keys are defined per-package, this allows applications which import packages integrating with common/config to automatically become configurable without adding boilerplate code in main.go.
*/
package config

import (
	"flag"
	"fmt"
	"os"
	"strings"
	"time"

	"code.justin.tv/common/golibs/errorlogger"
	"code.justin.tv/common/golibs/errorlogger/rollbar"
	"code.justin.tv/common/golibs/pkgpath"
	"github.com/cactus/go-statsd-client/statsd"
)

var (
	options      map[string]map[string]*string
	errorLogger  errorlogger.ErrorLogger
	statsRoot    statsd.Statter
	statsCluster statsd.Statter
	statsHost    statsd.Statter
)

func init() {
	main, _ := pkgpath.Main()
	sub := strings.Split(main, "/")
	Register(map[string]string{
		"environment":      "development",
		"statsd-host-port": "statsd.internal.justin.tv:8125",
		"rollbar-token":    "",
		"app":              sub[len(sub)-1],
	})

	statsCluster, _ = statsd.NewNoopClient()
	statsHost, _ = statsd.NewNoopClient()
}

// Register accepts a map of configuration keys with defaults. These fields only apply to the package which registers them.
func Register(fields map[string]string) {
	if options == nil {
		options = make(map[string]map[string]*string)
	}

	pkg, _ := pkgpath.Caller(1)

	if options[pkg] == nil {
		options[pkg] = make(map[string]*string)
	}

	for k, v := range fields {
		options[pkg][k] = flag.String(k, v, "")
	}
}

// Parse should be called by the main package to ensure all package-level configurations have been registered.
func Parse() error {
	flag.Parse()

	for _, opts := range options {
		for k := range opts {
			envVal, ok := checkEnvForField(k)
			if ok {
				opts[k] = &envVal
			}
		}
	}

	err := createClients()
	if err != nil {
		return err
	}

	return nil
}

// Resolve returns the configured value for a given key, based on command-line flags or environment variables.
// If a key cannot be resolved, it returns a blank string. Resolve must only be called after Parse.
func Resolve(field string) string {
	pkg, _ := pkgpath.Caller(1)
	return resolveWithPackage(field, pkg)
}

func resolveWithPackage(field, pkg string) string {
	packageOptions := options[pkg]
	resolved, ok := packageOptions[field]
	if !ok {
		return ""
	}
	return *resolved
}

// MustResolve returns the configured value for a given key using Resolve. If a key cannot be resolved, a panic is
// invoked. MustResolve must only be called after Parse.
func MustResolve(field string) string {
	pkg, _ := pkgpath.Caller(1)
	resolved := resolveWithPackage(field, pkg)
	if len(resolved) == 0 {
		message := fmt.Sprintf("\nMissing required config argument: %s\n\n", field)
		panic(message)
	}
	return resolved
}

// App returns the app name (as reported to statsd) of the current application.
func App() string {
	return Resolve("app")
}

// Environment returns the Twitch-specific deployment environment of the current application
func Environment() string {
	return Resolve("environment")
}

// StatsdHostPort returns the address to send statsd metrics
func StatsdHostPort() string {
	return Resolve("statsd-host-port")
}

// Statsd returns a ready-to-use stats.Statter to report application metrics with the hostname included. During test runs, this will return a Noop stats.Statter.
func Statsd() statsd.Statter {
	return statsHost
}

// ClusterStatsd returns a ready-to-use stats.Statter to report application metrics without a hostname. During test runs, this will return a Noop stats.Statter.
func ClusterStatsd() statsd.Statter {
	return statsCluster
}

// RollbarErrorLogger returns a ready-to-use client for reporting errors. If the "rollbar-token" configuration is blank, this is nil.
func RollbarErrorLogger() errorlogger.ErrorLogger {
	return errorLogger
}

func createClients() error {
	if Resolve("rollbar-token") != "" {
		errorLogger = rollbar.NewRollbarLogger(Resolve("rollbar-token"), Resolve("environment"))
	}

	if Resolve("statsd-host-port") != "" && Resolve("app") != "" && Resolve("app") != "_test" {
		hostName, err := os.Hostname()
		if err != nil {
			hostName = "unknown"
		}

		statsRoot, err = statsd.NewBufferedClient(Resolve("statsd-host-port"), fmt.Sprintf("%s.%s", Resolve("app"), Resolve("environment")), time.Second, 0)
		if err != nil {
			return err
		}

		statsCluster = statsRoot.NewSubStatter("all").(*statsd.Client)
		statsHost = statsRoot.NewSubStatter(hostName).(*statsd.Client)
	}

	return nil
}

func checkEnvForField(key string) (string, bool) {
	key = strings.Replace(strings.ToUpper(key), "-", "_", -1)

	val := os.Getenv(key)
	if val == "" {
		return "", false
	}

	return val, true
}
