package config

import (
	"crypto"
	"crypto/x509"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"code.vereign.com/code/viam-apis/clientutils"
	"code.vereign.com/code/viam-apis/config"
	"code.vereign.com/code/viam-apis/errors"

	"code.vereign.com/code/viam-apis/log"

	"code.vereign.com/code/viam-apis/authentication"
	"github.com/spf13/viper"

	_ "github.com/spf13/viper/remote"
)

var SystemAuth = &authentication.Authentication{
	Uuid:    "undefined",
	Session: "undefined",
}

type Configuration struct {
	ListenAddress        string `required:"false" yaml:"listenAddress"`
	GrpcListenAddress    string `required:"false" yaml:"grpcListenAddress"`
	SystemAuthentication struct {
		UUID    string `required:"false" yaml:"viamUUID"`
		Session string `required:"false" yaml:"viamSession"`
	} `required:"false" yaml:"systemAuthentication"`
	DataStorageAgentURL        string `required:"false" yaml:"dataStorageAgentURL"`
	EntitiesManagementAgentURL string `required:"false" yaml:"entitiesManagementAgentURL"`
	MaxMessageSize             int    `required:"false" yaml:"maxMessageSize"`
	Certification              struct {
		Path                      string              `required:"true" yaml:"path"`
		CertificateFile           string              `required:"true" yaml:"certificateFile"`
		CertificatePEM            []byte              `required:"true"`
		PrivateKeyFile            string              `required:"true" yaml:"privateKeyFile"`
		PrivateKeyPEM             []byte              `required:"true"`
		CaCertificateFile         string              `required:"true" yaml:"caCertificateFile"`
		CaCertificatePEM          []byte              `required:"true"`
		EncryptionCertificateFile string              `required:"true" yaml:"encryptionCertificateFile"`
		EncryptionCertificatePEM  []byte              `required:"true"`
		EncryptionCertificate     []*x509.Certificate `required:"true"`
		EncryptionPrivateKeyFile  string              `required:"true" yaml:"encryptionPrivateKeyFile"`
		EncryptionPrivateKeyPEM   []byte              `required:"true"`
		EncryptionPrivateKey      crypto.Signer       `required:"true"`
		VereignCertificateFile    string              `required:"true" yaml:"vereignCertificateFile"`
		VereignCertificatePEM     []byte              `required:"true"`
	} `required:"true" yaml:"certification"`
	PrometeusListenAddress  string `required:"false" yaml:"prometeusListenAddress"`
	MetricEnvironmentPrefix string `required:"false" yaml:"metricEnvironmentPrefix"`
	ReplaceKey              bool   `required:"false" yaml:"replaceKey"`
	GlobalLogLevel          string `required:"false" yaml:"globalLogLevel"`
}

var Config Configuration

// LoadConfigValues loads config values from config file
func LoadConfigValues(configFile, etcdURL string) error {
	setConfigValues(configFile, etcdURL)

	calculateConfigValues()

	err := config.CheckConfigValues(Config)
	if err != nil {
		log.Fatalf("Exiting due to wrong configuration: %s", err)
		return err
	}

	return nil
}

// SetConfigValues Sets Values For Config Variable
func setConfigValues(configFile, etcdURL string) {
	// Set default values for configuration variable
	// NOTE: Only for 'required:"false"' fields
	Config.ListenAddress = ":7878"
	Config.GrpcListenAddress = ":7877"
	Config.SystemAuthentication.UUID = "viam-system"
	Config.SystemAuthentication.Session = "viam-session"
	Config.DataStorageAgentURL = "data-storage-agent:7777"
	Config.EntitiesManagementAgentURL = "entities-management-agent:7779"
	Config.MaxMessageSize = 64
	Config.PrometeusListenAddress = ":2112"
	Config.MetricEnvironmentPrefix = "staging_data_storage_agent"
	Config.ReplaceKey = false
	Config.GlobalLogLevel = "info"

	// Read Config File
	if configFile != "" {
		configName := strings.Split(filepath.Base(configFile), ".")[0]
		configDir := filepath.Dir(configFile)
		viper.SetConfigName(configName)
		viper.AddConfigPath(configDir)
		if err := viper.ReadInConfig(); err != nil {
			log.Printf("can't read config: %v, will use default values", err)
		}
	} else {
		log.Printf("requesting config at "+etcdURL, "/"+os.Getenv("ENV_NAME")+"/"+os.Getenv("CI_PROJECT_NAME")+".json")
		viper.AddRemoteProvider("etcd", etcdURL, "/"+os.Getenv("ENV_NAME")+"/"+os.Getenv("CI_PROJECT_NAME")+".json")
		viper.SetConfigType("json")

		if err := viper.ReadRemoteConfig(); err != nil {
			log.Printf("can't read config: %v, will use default values", err)
		}
	}

	// Read Config File
	if configFile != "" {
		configName := strings.Split(filepath.Base(configFile), ".")[0]
		configDir := filepath.Dir(configFile)
		viper.SetConfigName(configName)
		viper.AddConfigPath(configDir)
		if err := viper.ReadInConfig(); err != nil {
			errors.LogFormat(err, "can't read config, will use default values")
		}
	} else {
		log.Printf("requesting config at "+etcdURL, "/"+os.Getenv("ENV_NAME")+"/"+os.Getenv("CI_PROJECT_NAME")+".json")
		viper.AddRemoteProvider("etcd", etcdURL, "/"+os.Getenv("ENV_NAME")+"/"+os.Getenv("CI_PROJECT_NAME")+".json")
		viper.SetConfigType("json")

		if err := viper.ReadRemoteConfig(); err != nil {
			errors.LogFormat(err, "can't read config, will use default values")
		}
	}

	err := viper.Unmarshal(&Config)
	if err != nil {
		log.Printf("unable to decode into config struct, %v", err)
	}
}

// CalculateConfigValues calculates config values using provided configuraion
func calculateConfigValues() {
	SystemAuth.Uuid = Config.SystemAuthentication.UUID
	SystemAuth.Session = Config.SystemAuthentication.Session

	Config.Certification.CertificatePEM = getCertificatePEM()
	Config.Certification.PrivateKeyPEM = getPrivateKeyPEM()
	Config.Certification.CaCertificatePEM = getCaCertificatePEM()
	Config.Certification.VereignCertificatePEM = getVereignCertificatePEM()
	Config.Certification.EncryptionCertificatePEM = getEncryptionCertificatePEM()
	Config.Certification.EncryptionPrivateKeyPEM = getEncryptionPrivateKeyPEM()

	var err error
	Config.Certification.EncryptionCertificate, Config.Certification.EncryptionPrivateKey, err = clientutils.LoadCertAndKey(Config.Certification.EncryptionCertificatePEM, Config.Certification.EncryptionPrivateKeyPEM)
	if err != nil {
		log.Printf("Load Err: %v", err)
	}
}

func getCertificatePEM() []byte {
	pem, err := ioutil.ReadFile(Config.Certification.Path + "/" + Config.Certification.CertificateFile)
	if err != nil {
		errors.Log(err)
		return []byte("")
	}
	return pem
}

func getCaCertificatePEM() []byte {
	pem, err := ioutil.ReadFile(Config.Certification.Path + "/" + Config.Certification.CaCertificateFile)
	if err != nil {
		errors.Log(err)
		return []byte("")
	}
	return pem
}

func getPrivateKeyPEM() []byte {
	pem, err := ioutil.ReadFile(Config.Certification.Path + "/" + Config.Certification.PrivateKeyFile)
	if err != nil {
		errors.Log(err)
		return []byte("")
	}
	return pem
}

func getVereignCertificatePEM() []byte {
	pem, err := ioutil.ReadFile(Config.Certification.Path + "/" + Config.Certification.CaCertificateFile)
	if err != nil {
		errors.Log(err)
		return []byte("")
	}
	return pem
}

func getEncryptionCertificatePEM() []byte {
	pem, err := ioutil.ReadFile(Config.Certification.Path + "/" + Config.Certification.EncryptionCertificateFile)
	if err != nil {
		errors.Log(err)
		return []byte("")
	}
	return pem
}

func getEncryptionPrivateKeyPEM() []byte {
	pem, err := ioutil.ReadFile(Config.Certification.Path + "/" + Config.Certification.EncryptionPrivateKeyFile)
	if err != nil {
		errors.Log(err)
		return []byte("")
	}
	return pem
}