package edgegrid

import (
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/go-ini/ini"
	"gopkg.in/mattes/go-expand-tilde.v1"
)

// Config struct provides all the necessary fields to
// create authorization header, debug is optional
type Config struct {
	Host         string   `ini:"host"`
	ClientToken  string   `ini:"client_token"`
	ClientSecret string   `ini:"client_secret"`
	AccessToken  string   `ini:"access_token"`
	HeaderToSign []string `ini:"headers_to_sign"`
	MaxBody      int      `ini:"max_body"`
	Debug        bool     `ini:"debug"`
}

// Init initializes by first attempting to use ENV vars, with .edgerc as a fallback
//
// See: InitEnv()
// See: InitEdgeRc()
func Init(filepath string, section string) (Config, error) {
	if section == "" {
		section = defaultSection
	} else {
		section = strings.ToUpper(section)
	}

	_, exists := os.LookupEnv("AKAMAI_" + section + "_HOST")
	if !exists && section == defaultSection {
		_, exists := os.LookupEnv("AKAMAI_HOST")

		if exists {
			return InitEnv("")
		}
	}

	if exists {
		return InitEnv(section)
	}

	c, err := InitEdgeRc(filepath, strings.ToLower(section))

	if err == nil {
		return c, nil
	}

	if section != defaultSection {
		_, ok := os.LookupEnv("AKAMAI_HOST")
		if ok {
			return InitEnv("")
		}
	}

	return c, fmt.Errorf("Unable to create instance using environment or .edgerc file")
}

// InitEdgeRc initializes using a configuration file in standard INI format
//
// By default, it uses the .edgerc found in the users home directory, and the
// "default" section.
func InitEdgeRc(filepath string, section string) (Config, error) {
	var (
		c               Config
		requiredOptions = []string{"host", "client_token", "client_secret", "access_token"}
		missing         []string
	)

	// Check if filepath is empty
	if filepath == "" {
		filepath = "~/.edgerc"
	}

	// Check if section is empty
	if section == "" {
		section = "default"
	}

	// Tilde seems to be not working when passing ~/.edgerc as file
	// Takes current user and use home dir instead

	path, err := tilde.Expand(filepath)

	if err != nil {
		return c, fmt.Errorf(errorMap[ErrHomeDirNotFound], err)
	}

	edgerc, err := ini.Load(path)
	if err != nil {
		return c, fmt.Errorf(errorMap[ErrConfigFile], err)
	}
	err = edgerc.Section(section).MapTo(&c)
	if err != nil {
		return c, fmt.Errorf(errorMap[ErrConfigFileSection], err)
	}
	for _, opt := range requiredOptions {
		if !(edgerc.Section(section).HasKey(opt)) {
			missing = append(missing, opt)
		}
	}
	if len(missing) > 0 {
		return c, fmt.Errorf(errorMap[ErrConfigMissingOptions], missing)
	}
	if c.MaxBody == 0 {
		c.MaxBody = 131072
	}
	return c, nil
}

// InitEnv initializes using the Environment (ENV)
//
// By default, it uses AKAMAI_HOST, AKAMAI_CLIENT_TOKEN, AKAMAI_CLIENT_SECRET,
// AKAMAI_ACCESS_TOKEN, and AKAMAI_MAX_BODY variables.
//
// You can define multiple configurations by prefixing with the section name specified, e.g.
// passing "ccu" will cause it to look for AKAMAI_CCU_HOST, etc.
//
// If AKAMAI_{SECTION} does not exist, it will fall back to just AKAMAI_.
func InitEnv(section string) (Config, error) {
	var (
		c               Config
		requiredOptions = []string{"HOST", "CLIENT_TOKEN", "CLIENT_SECRET", "ACCESS_TOKEN"}
		missing         []string
		prefix          string
	)

	// Check if section is empty
	if section == "" {
		section = defaultSection
	} else {
		section = strings.ToUpper(section)
	}

	prefix = "AKAMAI_"
	_, ok := os.LookupEnv("AKAMAI_" + section + "_HOST")
	if ok {
		prefix = "AKAMAI_" + section + "_"
	}

	for _, opt := range requiredOptions {
		val, ok := os.LookupEnv(prefix + opt)
		if !ok {
			missing = append(missing, prefix+opt)
		} else {
			switch {
			case opt == "HOST":
				c.Host = val
			case opt == "CLIENT_TOKEN":
				c.ClientToken = val
			case opt == "CLIENT_SECRET":
				c.ClientSecret = val
			case opt == "ACCESS_TOKEN":
				c.AccessToken = val
			}
		}
	}

	if len(missing) > 0 {
		return c, fmt.Errorf(errorMap[ErrMissingEnvVariables], missing)
	}

	c.MaxBody = 0

	val, ok := os.LookupEnv(prefix + "MAX_BODY")
	if i, err := strconv.Atoi(val); err == nil {
		c.MaxBody = i
	}

	if !ok || c.MaxBody == 0 {
		c.MaxBody = 131072
	}

	return c, nil
}
