/*
Configurations for load balancer server.
*/
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	bugout "github.com/bugout-dev/bugout-go/pkg"
	"github.com/google/uuid"
)

var (
	nodeConfigs []NodeConfig

	supportedBlockchains map[string]bool

	// Bugout client
	// TODO(kompotkot): Find out why it cuts out the port
	BUGOUT_BROOD_URL = "https://auth.bugout.dev"
	// BUGOUT_BROOD_URL              = os.Getenv("BUGOUT_BROOD_URL")
	NB_BUGOUT_TIMEOUT_SECONDS_RAW = os.Getenv("NB_BUGOUT_TIMEOUT_SECONDS")

	// Bugout and application configuration
	BUGOUT_AUTH_CALL_TIMEOUT        = time.Second * 5
	MOONSTREAM_APPLICATION_ID       = os.Getenv("MOONSTREAM_APPLICATION_ID")
	NB_CONTROLLER_TOKEN             = os.Getenv("NB_CONTROLLER_TOKEN")
	NB_CONTROLLER_ACCESS_ID         = os.Getenv("NB_CONTROLLER_ACCESS_ID")
	MOONSTREAM_CORS_ALLOWED_ORIGINS = os.Getenv("MOONSTREAM_CORS_ALLOWED_ORIGINS")

	NB_CONNECTION_RETRIES          = 2
	NB_CONNECTION_RETRIES_INTERVAL = time.Millisecond * 10
	NB_HEALTH_CHECK_INTERVAL       = time.Millisecond * 5000
	NB_HEALTH_CHECK_CALL_TIMEOUT   = time.Second * 2

	NB_CACHE_CLEANING_INTERVAL          = time.Second * 10
	NB_CACHE_ACCESS_ID_LIFETIME         = int64(120) // 2 minutes
	NB_CACHE_ACCESS_ID_SESSION_LIFETIME = int64(600) // 10 minutes

	NB_MAX_COUNTER_NUMBER = uint64(10000000)

	// Client configuration
	NB_CLIENT_NODE_KEEP_ALIVE = int64(5) // How long to store node in hot list for client in seconds

	NB_ACCESS_ID_HEADER   = os.Getenv("NB_ACCESS_ID_HEADER")
	NB_DATA_SOURCE_HEADER = os.Getenv("NB_DATA_SOURCE_HEADER")

	// Humbug configuration
	HUMBUG_REPORTER_NB_TOKEN = os.Getenv("HUMBUG_REPORTER_NB_TOKEN")

	// Moonstream resources types
	BUGOUT_RESOURCE_TYPE_NODEBALANCER_ACCESS   = "nodebalancer-access"
	DEFAULT_AUTOGENERATED_USER_PERMISSIONS     = []string{"read"}
	DEFAULT_AUTOGENERATED_PERIOD_DURATION      = int64(86400)
	DEFAULT_AUTOGENERATED_MAX_CALLS_PER_PERIOD = int64(1000)
)

func CreateBugoutClient() (bugout.BugoutClient, error) {
	bugoutTimeoutSeconds, err := strconv.Atoi(NB_BUGOUT_TIMEOUT_SECONDS_RAW)
	if err != nil {
		return bugout.BugoutClient{}, fmt.Errorf("unable to parse environment variable as integer: %v", err)
	}
	NB_BUGOUT_TIMEOUT_SECONDS := time.Duration(bugoutTimeoutSeconds) * time.Second

	broodClient := bugout.ClientBrood(BUGOUT_BROOD_URL, NB_BUGOUT_TIMEOUT_SECONDS)
	return broodClient, nil
}

func CheckEnvVarSet() {
	if NB_ACCESS_ID_HEADER == "" {
		NB_ACCESS_ID_HEADER = "x-node-balancer-access-id"
	}
	if NB_DATA_SOURCE_HEADER == "" {
		NB_DATA_SOURCE_HEADER = "x-node-balancer-data-source"
	}
	_, err := uuid.Parse(NB_CONTROLLER_ACCESS_ID)
	if err != nil {
		NB_CONTROLLER_ACCESS_ID = uuid.New().String()
		log.Printf("Access ID for internal usage in NB_CONTROLLER_ACCESS_ID environment variable is not valid uuid, generated random one: %v", NB_CONTROLLER_ACCESS_ID)
	}
}

// Nodes configuration
type NodeConfig struct {
	Blockchain string `json:"blockchain"`
	Endpoint   string `json:"endpoint"`
}

func LoadConfig(configPath string) error {
	rawBytes, err := ioutil.ReadFile(configPath)
	if err != nil {
		return err
	}
	nodeConfigsTemp := &[]NodeConfig{}
	err = json.Unmarshal(rawBytes, nodeConfigsTemp)
	if err != nil {
		return err
	}
	nodeConfigs = *nodeConfigsTemp
	return nil
}

type ConfigPlacement struct {
	ConfigDirPath   string
	ConfigDirExists bool

	ConfigPath   string
	ConfigExists bool
}

func CheckPathExists(path string) (bool, error) {
	var exists = true
	_, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			exists = false
		} else {
			return exists, fmt.Errorf("error due checking file path exists, err: %v", err)
		}
	}

	return exists, nil
}

func GetConfigPath(providedPath string) (*ConfigPlacement, error) {
	var configDirPath, configPath string
	if providedPath == "" {
		homeDir, err := os.UserHomeDir()
		if err != nil {
			return nil, fmt.Errorf("unable to find user home directory, %v", err)
		}
		configDirPath = fmt.Sprintf("%s/.nodebalancer", homeDir)
		configPath = fmt.Sprintf("%s/config.json", configDirPath)
	} else {
		configPath = strings.TrimSuffix(providedPath, "/")
		configDirPath = filepath.Dir(configPath)
	}

	configDirPathExists, err := CheckPathExists(configDirPath)
	if err != nil {
		return nil, err
	}
	configPathExists, err := CheckPathExists(configPath)
	if err != nil {
		return nil, err
	}

	config := &ConfigPlacement{
		ConfigDirPath:   configDirPath,
		ConfigDirExists: configDirPathExists,

		ConfigPath:   configPath,
		ConfigExists: configPathExists,
	}

	return config, nil
}

func GenerateDefaultConfig(config *ConfigPlacement) error {
	if !config.ConfigDirExists {
		if err := os.MkdirAll(config.ConfigDirPath, os.ModePerm); err != nil {
			return fmt.Errorf("unable to create directory, %v", err)
		}
		log.Printf("Config directory created at: %s", config.ConfigDirPath)
	}

	if !config.ConfigExists {
		tempConfig := []NodeConfig{
			{Blockchain: "ethereum", Endpoint: "http://127.0.0.1:8545"},
		}
		tempConfigJson, err := json.Marshal(tempConfig)
		if err != nil {
			return fmt.Errorf("unable to marshal configuration data, err: %v", err)
		}
		err = ioutil.WriteFile(config.ConfigPath, tempConfigJson, os.ModePerm)
		if err != nil {
			return fmt.Errorf("unable to write default config to file %s, err: %v", config.ConfigPath, err)
		}
		log.Printf("Created default configuration at %s", config.ConfigPath)
	}

	return nil
}
