package conf

import (
	"encoding/json"
	"errors"
	"fmt"
	"leafstalk/log"
	"strings"

	"github.com/nacos-group/nacos-sdk-go/v2/clients"
	"github.com/nacos-group/nacos-sdk-go/v2/clients/config_client"
	"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
	"github.com/nacos-group/nacos-sdk-go/v2/vo"
	"github.com/spf13/viper"
)

// LocalNacosConfig 本地nacos配置
type LocalNacosConfig struct {
	NamespaceId string          `json:"namespaceId"` // 空间命名
	LineGroup   string          `json:"lineGroup"`   // 线路分组
	PublicGroup string          `json:"publicGroup"` // 线路分组
	UserName    string          `json:"userName"`    //
	PassWord    string          `json:"passWord"`    //
	Servers     []*ConfigServer `json:"servers"`     // 集群服务器
}

type ConfigServer struct {
	IpAddr string `json:"ipAddr"` // ip地址
	Port   uint64 `json:"port"`   // 端口
}

// ConfigCenterAgent 配置中心代理
type ConfigCenterAgent struct {
	NacosClient config_client.IConfigClient // 客户端api
}

// 配置中心代理
func NewNacosConfigCenter(cfg *LocalNacosConfig, dataDirName string) (cli *ConfigCenterAgent, err error) {

	// 基本配置检查
	if cfg == nil || len(cfg.NamespaceId) == 0 { // len(cfg.DataID) == 0 ||
		err = errors.New("configuration check failed")
		return
	}

	// nacos客户端
	nacosClient, err := createNacosClient(cfg, dataDirName)
	if err != nil {
		return nil, err
	}

	cli = new(ConfigCenterAgent)
	cli.NacosClient = nacosClient
	return
}

// createNacosClient create config client.
func createNacosClient(cfg *LocalNacosConfig, dataDirName string) (iClient config_client.IConfigClient, err error) {
	clientConfig := *constant.NewClientConfig(
		constant.WithNamespaceId(cfg.NamespaceId), //When namespace is public, fill in the blank string here.
		constant.WithTimeoutMs(5000),
		constant.WithBeatInterval(1000),
		constant.WithNotLoadCacheAtStart(true),
		constant.WithLogDir(fmt.Sprintf("../nacos/%s/log", dataDirName)),
		constant.WithCacheDir(fmt.Sprintf("../nacos/%s/cache", dataDirName)),
		constant.WithLogLevel("debug"),
		constant.WithUsername(cfg.UserName),
		constant.WithPassword(cfg.PassWord),
	)

	var serverConfigs []constant.ServerConfig
	for _, serv := range cfg.Servers {
		c := constant.NewServerConfig(
			serv.IpAddr,
			serv.Port,
			constant.WithScheme("http"),
			constant.WithContextPath("/nacos"),
		)
		serverConfigs = append(serverConfigs, *c)
	}

	// Another way of create config client for dynamic configuration (recommend)
	return clients.NewConfigClient(
		vo.NacosClientParam{
			ClientConfig:  &clientConfig,
			ServerConfigs: serverConfigs,
		},
	)
}

// GetIClient get client api
func (c *ConfigCenterAgent) GetIClient() config_client.IConfigClient {
	return c.NacosClient
}

// GetConfig get config info
func (c *ConfigCenterAgent) GetConfig(dataId string, group string) (string, error) {
	return c.NacosClient.GetConfig(vo.ConfigParam{
		DataId: dataId,
		Group:  group,
	})
}

// PublishConfig publish config
func (c *ConfigCenterAgent) PublishConfig(content string, dataId string, group string) (bool, error) {
	return c.NacosClient.PublishConfig(vo.ConfigParam{
		DataId:  dataId,
		Group:   group,
		Content: content,
	})
}

// DeleteConfig delete config
func (c *ConfigCenterAgent) DeleteConfig(dataId string, group string) (bool, error) {
	return c.NacosClient.DeleteConfig(vo.ConfigParam{
		DataId: dataId,
		Group:  group,
	})
}

// CancelListenConfig Cancel the listening of config change event
func (c *ConfigCenterAgent) CancelListenConfig(dataId string, group string) error {
	return c.NacosClient.CancelListenConfig(vo.ConfigParam{
		DataId: dataId,
		Group:  group,
	})
}

// ListenChanges Listen config change event
func (c *ConfigCenterAgent) ListenChanges(dataId string, group string, onChange func(namespace, group, dataId, data string)) error {
	return c.NacosClient.ListenConfig(vo.ConfigParam{
		DataId:   dataId,
		Group:    group,
		OnChange: onChange,
	})
}

// LoadSysConfig 加载系统配置
// 先从本地拿到nacos配置，然后到nacos中拿系统配置
func LoadSysConfig(cfgFile string, appName string) (sub1 *Config, err error) {
	conf1 := viper.New()
	conf1.SetConfigFile(cfgFile) //"../conf/grave.json"
	if err = conf1.ReadInConfig(); err != nil {
		log.Warnf("LoadSysConfig ReadInConfig error %v", err)
		return
	}

	// 取nacos相关配置
	var nacosCfg *LocalNacosConfig
	if err = conf1.UnmarshalKey("nacos", &nacosCfg); err != nil {
		log.Warnf("LoadSysConfig UnmarshalKey error %v", err)
		return
	}

	if nacosCfg == nil {
		return nil, errors.New("no config")
	}
	log.Infof("LoadSysConfig nacosCfg %v", *nacosCfg)

	// 构成配置中心
	cfgCenter, err := NewNacosConfigCenter(nacosCfg, appName)
	if err != nil {
		log.Warnf("LoadSysConfig NewNacosConfigCenter error %v", err)
		return
	}

	// 初始化动态配置事件
	err = initDynamicEvent(cfgCenter, nacosCfg.PublicGroup)
	if err != nil {
		log.Warnf("LoadSysConfig initDynamicEvent error %v", err)
		return
	}

	// 从配置中心拿静态配置
	content, err := cfgCenter.GetConfig(appName, nacosCfg.LineGroup)
	if err != nil {
		log.Warnf("LoadSysConfig GetConfig %v error %v", nacosCfg.LineGroup, err)
		return
	}

	if err = conf1.MergeConfig(strings.NewReader(content)); err != nil {
		log.Warnf("LoadSysConfig MergeConfig %v error %v %v", nacosCfg.LineGroup, content, err)
		return
	}

	// // 共享组配置
	// content2, err2 := cfgCenter.GetConfig(appName, nacosCfg.PublicGroup)
	// if err2 != nil {
	// 	log.Warnf("LoadSysConfig GetConfig %v error %v", nacosCfg.PublicGroup, err2)
	// 	return
	// }
	// if err = conf1.MergeConfig(strings.NewReader(content2)); err != nil {
	// 	log.Warnf("LoadSysConfig MergeConfig %v error %v %v", nacosCfg.PublicGroup, content, err)
	// 	return
	// }

	// 拿各模块配置
	includeList := conf1.GetStringSlice("include")
	for _, v := range includeList {
		dataId := v
		lineGroup := nacosCfg.LineGroup
		ss := strings.Split(v, ".")
		if len(ss) == 2 {
			lineGroup = ss[0]
			dataId = ss[1]
		}
		// // 分析一条包含项， 获取到线路、数据项，选择项
		// lineGroup, dataId, opts, err := parseIncludeItem(dataId, lineGroup)
		// if err != nil {
		// 	log.Warnf("LoadSysConfig parseIncludeItem error %v", err)
		// 	return nil, err
		// }

		// 先获取数据项
		var subContent string
		if subContent, err = cfgCenter.GetConfig(dataId, lineGroup); err != nil {
			log.Warnf("LoadSysConfig GetConfig %v error2 %v", dataId, err)
			return nil, err
		}

		var data map[string]interface{}
		if err = json.Unmarshal([]byte(subContent), &data); err != nil {
			log.Warnf("LoadSysConfig Unmarshal error2 %v", err)
			err = fmt.Errorf("load include sub:%v, err:%+v", dataId, err)
			return nil, err
		}

		// // 调整可选项
		// data, err = adjustOpts(dataId, data, opts)
		// if err != nil {
		// 	return nil, err
		// }

		conf1.Set(dataId, data)
	}

	for _, key := range conf1.AllKeys() {
		value := conf1.Get(key)
		fmt.Printf("%s: %v\n", key, value)
	}

	sub1 = NewConfig(conf1)
	return
}

// ["etcd", "nats", "sharedRedis", "lines.etcd", "lines.nats.[gmSbject.line1]", "lines.redis.[line1]"  ]
// 从.[分割，前面是数据项；后面是可选项
// 数据项单项：默认数据组做数据组 单项值做数据ID
// 数据项双项：第一项是数据组，第二项是数据ID
// 可选项：可以有多个可选项
func parseIncludeItem(itemVal string, defaultLineGroup string) (lineGroup string, dataId string, opts []string, err error) {

	splitDataId := func(val string) (group string, dataId string, err error) {
		vals := strings.Split(val, ".")
		if len(vals) == 1 {
			return defaultLineGroup, vals[0], nil
		} else if len(vals) == 2 {
			return vals[0], vals[1], nil
		}
		return "", "", errors.New("error dataId format")
	}

	// 用.[分割字符串
	vals := strings.Split(itemVal, ".[")
	num := len(vals)
	if num != 1 && num != 2 {
		return "", "", nil, errors.New("error dataId format1")
	}
	if num == 2 {
		vals[1] = strings.TrimRight(vals[1], "]")
	}

	// 先分割数据项
	lineGroup, dataId, err = splitDataId(vals[0])
	if err != nil {
		return "", "", nil, errors.New("error dataId format2")
	}
	if num == 1 {
		return lineGroup, dataId, nil, nil
	}

	// 分割选项
	opts = strings.Split(vals[1], ",")
	return lineGroup, dataId, opts, nil
}

// "lines.redis.[line1]"; "lines.redis.[db.line1]"; "lines.redis.[db.line1, addr.line1]"
// 所有非可选项 + 可选项 做数据项的值.可以有多个可选项. 针对每个子可选项：
// 分割后只有一项：单项值做数据项的值；只有一项时，不能有其他可选项，例如不能lines.redis.[line1， addr.line1]
// 分割后有2项：第二项的值提取出来，做第一项的值
func adjustOpts(dataId string, data map[string]interface{}, opts []string) (map[string]interface{}, error) {

	for _, opt := range opts {

		path := strings.Split(opt, ".")

		if len(path) == 1 {
			// 子项做主项，只能一个，并且没有兄弟子项
			if len(opts) != 1 {
				return nil, errors.New("errors opts format1")
			}
			data[dataId] = data[opt]
			return data, nil

		} else if len(path) == 2 {
			// 孙子项替换子项 lines.redis.[db.line1]
			childKey := path[0]
			grandsonKey := path[1]

			childVal, ok := data[childKey]
			if !ok {
				return nil, errors.New("errors opts format2")
			}
			grandsons, ok := childVal.(map[string]interface{})
			if !ok {
				return nil, errors.New("errors opts format3")
			}
			data[childKey], ok = grandsons[grandsonKey]
			if !ok {
				return nil, errors.New("errors opts format4")
			}
			continue
		}

		return nil, errors.New("errors opts formatn")
	}
	return data, nil
}
