//go:generate goagen bootstrap -d code.justin.tv/dta/necronomicon-user-api/design

package main

import (
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"

	"code.justin.tv/dta/necronomicon-user-api/app"
	metrics "github.com/armon/go-metrics"
	"github.com/armon/go-metrics/datadog"
	"github.com/goadesign/goa"
	"github.com/goadesign/goa/middleware"
	"github.com/goamz/goamz/aws"
	"github.com/goamz/goamz/s3"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

const (
	DocumentFormatVersion = 1
	UnknownUser           = "UNKNOWN_USER" // Used when testing and there is no auth headers
)

var (
	configFiles []string
	config      *viper.Viper
)

func init() {
	config = viper.New()
	config.SetConfigType("json")
	config.SetDefault("dta/necronomicon.db.username", "necronomicon")
	config.SetDefault("dta/necronomicon.db.hostname", "localhost")
	config.SetDefault("dta/necronomicon.db.password", "necronomicon")
	config.SetDefault("dta/necronomicon.db.port", 5432)
	config.SetDefault("dta/necronomicon.db.database", "necronomicon")
	config.SetDefault("dta/necronomicon.s3.bucket", "tw-dta-necronomicon-dev")
	config.SetDefault("dta/necronomicon.github.url", "https://git-aws.internal.justin.tv")
}

// Resources is a collection of configuration dependent resources that a
// controller method should copy at the start of execution.
type Resources struct {
	db     *DB
	bucket BucketPutter
}

func main() {
	var cmd = &cobra.Command{
		Use:               "necro",
		Short:             "Necronomicon is a configuration management service",
		Long:              `Necronomicon is a configuration management service`,
		PersistentPreRunE: setConfig,
		RunE:              runCmd,
	}
	cmd.PersistentFlags().StringP("config", "c", "/etc/necronomicon/server.json", "config file")
	cmd.PersistentFlags().StringP("secrets", "s", "", "secrets file")

	if err := cmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func setConfig(cmd *cobra.Command, args []string) error {
	configFile, err := cmd.PersistentFlags().GetString("config")
	if err != nil {
		return fmt.Errorf("Could not get config flag: %v", err)
	}
	configFiles = append(configFiles, configFile)
	secretsFile, err := cmd.PersistentFlags().GetString("secrets")
	if err != nil {
		return fmt.Errorf("Could not get secrets flag: %v", err)
	}
	if secretsFile != "" {
		configFiles = append(configFiles, secretsFile)
	}
	return nil
}

func loadConfig() (*Resources, error) {
	resources := new(Resources)
	config.SetConfigFile(configFiles[0])
	err := config.ReadInConfig()
	if err != nil {
		return nil, fmt.Errorf("Could not read config file %v", err)
	}
	for _, file := range configFiles[1:] {
		config.SetConfigFile(file)
		err := config.MergeInConfig()
		if err != nil {
			return nil, fmt.Errorf("Error processing config file %v", err)
		}
	}
	dbUsername, dbPassword, dbHostname, dbPort, dbDatabase := config.GetString("dta/necronomicon.db.username"), config.GetString("dta/necronomicon.db.password"), config.GetString("dta/necronomicon.db.hostname"), config.GetInt("dta/necronomicon.db.port"), config.GetString("dta/necronomicon.db.database")
	dbString := fmt.Sprintf("postgres://%s:%s@%s:%d/%s", dbUsername, dbPassword, dbHostname, dbPort, dbDatabase)
	resources.db = NewDB(dbString)
	auth, err := aws.GetAuth("", "", "", time.Time{})
	if err != nil {
		return nil, fmt.Errorf("Could not authorize to AWS: %v", err)
	}
	s3Bucket := config.GetString("dta/necronomicon.s3.bucket")
	s3 := s3.New(auth, aws.USWest2)
	resources.bucket = s3.Bucket(s3Bucket)
	return resources, nil
}

func loadAndProcessConfig(deploymentController *DeploymentController, environmentController *EnvironmentController, snapshotController *SnapshotController) error {
	resources, err := loadConfig()
	if err != nil {
		return fmt.Errorf("Could not load config file %v", err)
	}
	deploymentController.resources = resources
	environmentController.resources = resources
	snapshotController.resources = resources
	return nil
}

func setupMetrics() {
	hostname, err := os.Hostname()
	if err != nil {
		log.Fatal(err)
	}
	dd, err := datadog.NewDogStatsdSink("127.0.0.1:8125", hostname)
	if err != nil {
		log.Fatalf("Could not create datadog statsd client: %v", err)
	}
	tags := []string{"Devtools_service:necronomicon"}
	if region := config.GetString("_hound.aws.region"); region != "" {
		tags = append(tags, "datacenter:"+region)
	}
	if environment := config.GetString("_hound.environment"); environment != "" {
		tags = append(tags, "environment:"+environment)
	}
	dd.SetTags(tags)
	m, err := metrics.New(metrics.DefaultConfig("necronomicon"), dd)
	if err != nil {
		log.Fatalf("Could not create metrics sink: %v", err)
	}
	goa.SetMetrics(m)
}

func loadMiddlewares(service *goa.Service) {
	service.Use(middleware.RequestID())
	service.Use(middleware.LogRequest(true))
	service.Use(middleware.ErrorHandler(service, true))
	service.Use(middleware.Recover())
	app.UseBasicAuthMiddleware(service, NewBasicAuthMiddleware())
}

func runCmd(cmd *cobra.Command, args []string) error {
	setupMetrics()
	service := goa.New("Necronomicon User API")
	loadMiddlewares(service)
	deploymentController := NewDeploymentController(service)
	environmentController := NewEnvironmentController(service)
	publicController := NewPublicController(service)
	snapshotController := NewSnapshotController(service)
	err := loadAndProcessConfig(deploymentController, environmentController, snapshotController)
	if err != nil {
		return fmt.Errorf("Could not load config file %v", err)
	}
	app.MountDeploymentController(service, deploymentController)
	app.MountEnvironmentController(service, environmentController)
	app.MountPublicController(service, publicController)
	app.MountSnapshotController(service, snapshotController)
	ch := make(chan os.Signal)
	signal.Notify(ch, syscall.SIGHUP)
	go func() {
		for range ch {
			service.LogInfo("Reloading config")
			err := loadAndProcessConfig(deploymentController, environmentController, snapshotController)
			if err != nil {
				service.LogError("Could not load config file %v", err)
			}
		}
	}()
	if err := service.ListenAndServe(":8080"); err != nil {
		service.LogError("startup", "err", err)
		return fmt.Errorf("Could not start server: %v", err)
	}
	return nil
}
