// Copyright 2019 Sorint.lab
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"io/ioutil"
	"time"

	"github.com/pkg/errors"
	yaml "gopkg.in/yaml.v2"
)

type Config struct {
	Gateway             Gateway             `yaml:"gateway"`
	Scheduler           Scheduler           `yaml:"scheduler"`
	RunServiceScheduler RunServiceScheduler `yaml:"runServiceScheduler"`
	RunServiceExecutor  RunServiceExecutor  `yaml:"runServiceExecutor"`
	ConfigStore         ConfigStore         `yaml:"configStore"`
	GitServer           GitServer           `yaml:"gitServer"`
}

type Gateway struct {
	Debug bool `yaml:"debug"`

	// APIExposedURL is the gateway API exposed url i.e. https://myagola.example.com
	APIExposedURL string `yaml:"apiExposedURL"`

	// ExposedURL is the web interface exposed url i.e. https://myagola.example.com
	// This is used for generating the redirect_url in oauth2 redirects
	WebExposedURL string `yaml:"webExposedURL"`

	RunServiceURL  string `yaml:"runServiceURL"`
	ConfigStoreURL string `yaml:"configStoreURL"`

	Web  Web  `yaml:"web"`
	Etcd Etcd `yaml:"etcd"`
	LTS  LTS  `yaml:"lts"`

	TokenSigning TokenSigning `yaml:"tokenSigning"`

	AdminToken string `yaml:"adminToken"`
}

type Scheduler struct {
	Debug bool `yaml:"debug"`

	RunServiceURL string `yaml:"runServiceURL"`
}

type RunServiceScheduler struct {
	Debug bool `yaml:"debug"`

	DataDir string `yaml:"dataDir"`
	Web     Web    `yaml:"web"`
	Etcd    Etcd   `yaml:"etcd"`
	LTS     LTS    `yaml:"lts"`
}

type RunServiceExecutor struct {
	Debug bool `yaml:"debug"`

	DataDir string `yaml:"dataDir"`

	RunServiceURL string `yaml:"runServiceURL"`
	ToolboxPath   string `yaml:"toolboxPath"`

	Web Web `yaml:"web"`
}

type ConfigStore struct {
	Debug bool `yaml:"debug"`

	DataDir string `yaml:"dataDir"`

	Web  Web  `yaml:"web"`
	Etcd Etcd `yaml:"etcd"`
	LTS  LTS  `yaml:"lts"`
}

type GitServer struct {
	Debug bool `yaml:"debug"`

	DataDir string `yaml:"dataDir"`

	GithookPath string `yaml:"githookPath"`
	GatewayURL  string `yaml:"gatewayURL"`

	Web  Web  `yaml:"web"`
	Etcd Etcd `yaml:"etcd"`
	LTS  LTS  `yaml:"lts"`
}

type Web struct {
	// http listen addess
	ListenAddress string `yaml:"listenAddress"`

	// use TLS (https)
	TLS bool `yaml:"tls"`
	// TLSCert is the path to the pem formatted server certificate. If the
	// certificate is signed by a certificate authority, the certFile should be
	// the concatenation of the server's certificate, any intermediates, and the
	// CA's certificate.
	TLSCertFile string `yaml:"tlsCertFile"`
	// Server cert private key
	// TODO(sgotti) support encrypted private keys (add a private key password config entry)
	TLSKeyFile string `yaml:"tlsKeyFile"`

	// CORS allowed origins
	AllowedOrigins []string `yaml:"allowedOrigins"`
}

type LTSType string

const (
	LTSTypePosix LTSType = "posix"
	LTSTypeS3    LTSType = "s3"
)

type LTS struct {
	Type LTSType `yaml:"type"`

	// Posix
	Path string `yaml:"path"`

	// S3
	Endpoint        string `yaml:"endpoint"`
	Bucket          string `yaml:"bucket"`
	Location        string `yaml:"location"`
	AccessKey       string `yaml:"accessKey"`
	SecretAccessKey string `yaml:"secretAccessKey"`
	DisableTLS      bool   `yaml:"disableTLS"`
}

type Etcd struct {
	Endpoints string `yaml:"endpoints"`

	// TODO(sgotti) support encrypted private keys (add a private key password config entry)
	TLSCertFile   string `yaml:"tlsCertFile"`
	TLSKeyFile    string `yaml:"tlsKeyFile"`
	TLSCAFile     string `yaml:"tlsCAFile"`
	TLSSkipVerify bool   `yaml:"tlsSkipVerify"`
}

type TokenSigning struct {
	// token duration (defaults to 12 hours)
	Duration time.Duration `yaml:"duration"`
	// signing method: "hmac" or "rsa"
	Method string `yaml:"method"`
	// signing key. Used only with HMAC signing method
	Key string `yaml:"key"`
	// path to a file containing a pem encoded private key. Used only with RSA signing method
	PrivateKeyPath string `yaml:"privateKeyPath"`
	// path to a file containing a pem encoded public key. Used only with RSA signing method
	PublicKeyPath string `yaml:"publicKeyPath"`
}

var defaultConfig = Config{
	Gateway: Gateway{
		TokenSigning: TokenSigning{
			Duration: 12 * time.Hour,
		},
	},
}

func Parse(configFile string) (*Config, error) {
	configData, err := ioutil.ReadFile(configFile)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	c := &defaultConfig
	if err := yaml.Unmarshal(configData, &c); err != nil {
		return nil, errors.WithStack(err)
	}

	return c, Validate(c)
}

func validateWeb(w *Web) error {
	if w.ListenAddress == "" {
		return errors.Errorf("listen address undefined")
	}

	if w.TLS {
		if w.TLSKeyFile == "" {
			return errors.Errorf("no tls key file specified")
		}
		if w.TLSCertFile == "" {
			return errors.Errorf("no tls cert file specified")
		}
	}

	return nil
}

func Validate(c *Config) error {
	// Gateway
	if c.Gateway.APIExposedURL == "" {
		return errors.Errorf("gateway apiExposedURL is empty")
	}
	if c.Gateway.WebExposedURL == "" {
		return errors.Errorf("gateway webExposedURL is empty")
	}
	if c.Gateway.ConfigStoreURL == "" {
		return errors.Errorf("gateway configStoreURL is empty")
	}
	if c.Gateway.RunServiceURL == "" {
		return errors.Errorf("gateway runServiceURL is empty")
	}
	if err := validateWeb(&c.Gateway.Web); err != nil {
		return errors.Wrapf(err, "gateway web configuration error")
	}

	// Configstore
	if c.ConfigStore.DataDir == "" {
		return errors.Errorf("configstore dataDir is empty")
	}
	if err := validateWeb(&c.ConfigStore.Web); err != nil {
		return errors.Wrapf(err, "configstore web configuration error")
	}

	// Runservice Scheduler
	if c.RunServiceScheduler.DataDir == "" {
		return errors.Errorf("runservice scheduler dataDir is empty")
	}
	if err := validateWeb(&c.RunServiceScheduler.Web); err != nil {
		return errors.Wrapf(err, "runservice scheduler web configuration error")
	}

	// Runservice Executor
	if c.RunServiceExecutor.DataDir == "" {
		return errors.Errorf("runservice executor dataDir is empty")
	}
	if c.RunServiceExecutor.ToolboxPath == "" {
		return errors.Errorf("git server toolboxPath is empty")
	}
	if c.RunServiceExecutor.RunServiceURL == "" {
		return errors.Errorf("runservice executor runServiceURL is empty")
	}

	// Scheduler
	if c.Scheduler.RunServiceURL == "" {
		return errors.Errorf("scheduler runServiceURL is empty")
	}

	// Git server
	if c.GitServer.DataDir == "" {
		return errors.Errorf("git server dataDir is empty")
	}
	if c.GitServer.GithookPath == "" {
		return errors.Errorf("git server githookPath is empty")
	}
	if c.GitServer.GatewayURL == "" {
		return errors.Errorf("git server gatewayURL is empty")
	}

	return nil
}