package config

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"

	"github.com/fsnotify/fsnotify"

	"go.etcd.io/etcd/client"
)

var Conf *Configure

const ConfigPath = "conf/common.json"

// mysql 配置
type MysqlConfig struct {
	User        string      `json:"user"`
	Password    string      `json:"password"`
	Addr        string      `json:"addr"`
	DefaultDB   string      `json:"default_db"`
	Charset     string      `json:"charset"`
	MaxIdle     json.Number `json:"max_idle"`
	MaxConn     json.Number `json:"max_conn"`
	ServiceName string      `json:"service_name"`
	ServicePort int         `json:"service_port"`
}

// redis 配置
type RedisConfig struct {
	Addrs        string      `json:"addrs"`
	Password     string      `json:"password"`
	DefaultDB    json.Number `json:"default_db"`
	PoolSize     json.Number `json:"pool_size"`
	MinIdleConns json.Number `json:"min_idle_conns"`
	MaxRetries   json.Number `json:"max_retries"`
	IsCluster    string      `json:"is_cluster"`
}

type ElasticConfig struct {
	Addr  string `json:"addr"`
	Sniff string `json:"sniff"`
}

type LogConfig struct {
	MaxSize           json.Number `json:"max_size"`
	MaxBackups        json.Number `json:"max_backups"`
	MaxAge            json.Number `json:"max_age"`
	DisableStacktrace string      `json:"disable_stacktrace"`
}

type CGINode struct {
	Log LogConfig `json:"log"`
}

type Gateway struct {
	AccountLockTimestamp json.Number `json:"account_lock_timestamp"`
	AccountLockErrors    json.Number `json:"account_lock_errors"`
	TokenExpired         json.Number `json:"token_expired"`
	Log                  LogConfig   `json:"log"`
	ServiceName    string      `json:"service_name"`
	ServicePort    json.Number          `json:"service_port"`
	Name string `json:"name"`
}

type CGIConfig struct {
	Gateway Gateway `json:"gd_management_gateway"`
}

type RPCNode struct {
	Scheme         string      `json:"scheme"`
	Name           string      `json:"name"`
	UpdateInterval json.Number `json:"update_interval"`
	Log            LogConfig   `json:"log"`
	ServiceName    string      `json:"service_name"`
	ServicePort    json.Number         `json:"service_port"`
}

type RPCConfig struct {
	BasePath   string  `json:"base_path"`
	Service    RPCNode `json:"gd_service"`
	Vehicle    RPCNode `json:"gd_vehicle"`
	Management RPCNode `json:"gd_management"`
	Admin      RPCNode `json:"gd_admin"`
	Statistics  RPCNode `json:"gd_statistics"`
	Crontab    RPCNode `json:"gd_crontab"`
}

type OssConfig struct {
	Endpoint string `json:"endpoint"`
	Id       string `json:"id"`
	Secret   string `json:"secret"`
	Bucket   string `json:"bucket"`
}

type Configure struct {
	RunMode   string        `json:"run_mode"`
	AppKey    string        `json:"app_key"`
	AppSecret string        `json:"app_secret"`
	Mysql     MysqlConfig   `json:"mysql"`
	Redis     RedisConfig   `json:"redis"`
	Elastic   ElasticConfig `json:"elastic"`
	Cgi       CGIConfig     `json:"cgi"`
	Rpc       RPCConfig     `json:"rpc"`
	Oss       OssConfig     `json:"oss"`
}

// key 为加密密钥
func GetConfig(runmode, key string, cli client.Client) *Configure {
	keysAPI := client.NewKeysAPI(cli)
	basePath := fmt.Sprintf("/%s/config", runmode)
	if resp, err := keysAPI.Get(context.Background(), basePath, &client.GetOptions{
		Recursive: true,
	}); err == nil && resp != nil && resp.Node != nil {
		Conf = &Configure{}
		value := getNodeData(key, resp.Node)
		if jsonStr, err := json.Marshal(value); err == nil {
			if err := json.Unmarshal(jsonStr, &Conf); err == nil {
				return Conf
			} else {
				fmt.Printf("json Unmarshal failed. error:%s", err)
			}
		} else {
			fmt.Printf("json Marshal failed. error:%s", err)
		}
	} else {
		fmt.Printf("get %s failed. error:%s", basePath, err)
		os.Exit(1)
	}

	return nil
}

// 递归取出node的叶子节点值
func getNodeData(key string, head *client.Node) (value interface{}) {
	s0 := strings.Split(head.Key, "/")
	len0 := len(s0)
	if len0 == 0 {
		return
	}

	if head.Dir {
		mapData := map[string]interface{}{}
		for _, node := range head.Nodes {
			s1 := strings.Split(node.Key, "/")
			len1 := len(s1)
			if len1 == 0 {
				break
			}

			mapData[s1[len1-1]] = getNodeData(key, node)
		}

		value = mapData
	} else {
		if key != "" && head.Value != "" {
			if bytesData, err := Base64URLDecode(head.Value); err != nil {
				fmt.Printf("Base64URLDecode(%s) failed. error:%s", head.Value, err)
				os.Exit(1)
			} else {
				if data, err := AesDecrypt(bytesData, []byte(key)); err != nil {
					fmt.Printf("AesDecrypt failed. error:%s", err)
					os.Exit(1)
				} else {
					value = string(data)
				}
			}
		} else {
			// 无加密，直接取值
			value = head.Value
		}
	}

	return
}

func GetConfigForK8s() *Configure {
	buffer, err := ioutil.ReadFile(ConfigPath)
	if err != nil {
		fmt.Printf("get %s failed. error:%s", ConfigPath, err)
		return nil
	}
	Conf = &Configure{}
	if err := json.Unmarshal(buffer, Conf); err != nil {
		fmt.Printf("json Unmarshal failed. error:%s", err)
		return nil
	}
	go watchConfigFileForK8s()
	return Conf
}

func ReloadConfigForK8s() {
	buffer, err := ioutil.ReadFile(ConfigPath)
	if err != nil {
		fmt.Printf("get %s failed. error:%s", ConfigPath, err)
	}
	confTmp := &Configure{}
	if err := json.Unmarshal(buffer, confTmp); err != nil {
		fmt.Printf("json Unmarshal failed. error:%s", err)
	}
	Conf = confTmp
}

// 判断路径文件/文件夹是否存在
func Exists(path string) bool {
	_, err := os.Stat(path)
	if err != nil {
		if os.IsExist(err) {
			return true
		}
		return false
	}
	return true
}

func watchConfigFileForK8s() {
	fileExist := true
	watch, err := fsnotify.NewWatcher()
	if err != nil {
		fmt.Printf("new file watcher failed\n\n")
		os.Exit(1)
	}

	defer watch.Close()

	for {
		// 判断文件是否存在
		if !Exists(ConfigPath) {
			time.Sleep(10 * time.Second)
			fileExist = false
			continue
		} else {
			watch.Remove(ConfigPath)
			watch.Add(ConfigPath)
			if !fileExist { // 文件重新创建
				ReloadConfigForK8s()
			}
			fileExist = true
		}

		select {
		case ev := <-watch.Events:
			{
				fmt.Println("op : ", ev.Op)
				ReloadConfigForK8s()

			}
		case err := <-watch.Errors:
			{
				fmt.Println("error : ", err)
				continue
			}
		}
	}
}
