package actions

import (
	"fmt"
	"log/slog"
	"strconv"
	"zzman/clients/jd"
	"zzman/model"
	u "zzman/util"

	"gorm.io/gorm"
)

type UpdateArgs struct {
	Mock bool
}

func Update(tx *gorm.DB, args ...UpdateArgs) error {
	var arg UpdateArgs
	if len(args) > 0 {
		arg = args[0]
	} else {
		arg.Mock = false
	}

	gateways, err := FindGateways(tx)
	if err != nil {
		return fmt.Errorf("获取所有网关失败：%w", err)
	}

	cities, err := FindCitiesWithEdgesCount(tx)
	if err != nil {
		return fmt.Errorf("获取所有城市失败：%w", err)
	}

	// 准备网关查找表，初始化网关配置表
	type ConfigInfo struct {
		Item jd.EdgeInfo
		Type int // 0: 不变, 1: 更新, 2: 新增
	}
	var oldConfigsMap = make(map[model.Gateway]map[string]model.Config)
	var newConfigs = make(map[model.Gateway][]ConfigInfo)
	for _, gateway := range gateways {
		newConfigs[gateway] = make([]ConfigInfo, len(cities))

		oldConfigs, err := FindConfigsByGateway(tx, gateway.Macaddr)
		if err != nil {
			return fmt.Errorf("获取网关 %s 城市节点失败：%w", gateway.Macaddr, err)
		}

		oldConfigsMap[gateway] = make(map[string]model.Config)
		for _, config := range oldConfigs {
			oldConfigsMap[gateway][config.Cityhash] = config
		}
	}

	// 按城市循环，对比并更新网关配置
	for iCity, city := range cities {

		// 如果每个网关在此城市都有节点且无需改变，就不需要从云端拉取城市节点信息
		// 相当于直接重新提交配置，此流程下配置更新是幂等的
		var gateways2Update []model.Gateway
		for _, gateway := range gateways {
			oldConfig, exists := oldConfigsMap[gateway][city.Hash]
			if exists && oldConfig.IsChange != 1 {
				newConfigs[gateway][iCity] = ConfigInfo{
					Type: 0, // 不变
					Item: jd.EdgeInfo{
						Mac:  oldConfig.Macaddr,
						City: oldConfig.Cityhash,
					},
				}
			} else {
				gateways2Update = append(gateways2Update, gateway)
			}
		}
		if len(gateways2Update) == 0 {
			continue
		}

		// 否则，正常从云端拉取城市节点信息
		offset := city.Offset
		count := len(gateways2Update)
		if count > city.EdgesCount {
			slog.Warn("城市节点数量不足，跳过本次更新", "城市", city.Name, "节点数", city.EdgesCount, "网关数", count)
			continue
		}

		if offset+count > city.EdgesCount {
			slog.Debug("城市节点不足，将循环使用节点", "城市", city.Name, "节点数", city.EdgesCount, "网关数", count)
			offset = 0
		}

		edges, err := SliceActiveEdgesByCity(tx, city.Id, offset, count)
		if err != nil {
			return fmt.Errorf("查询城市 %s 可用节点失败：%w", city.Name, err)
		}

		// 更新网关配置
		var configs2Create []model.Config
		var configs2Update []model.ConfigUpdate

		for iGateway, gateway := range gateways2Update {

			oldConfig, exists := oldConfigsMap[gateway][city.Hash]
			newConfig := edges[iGateway]

			if exists {
				fmt.Printf("\t网关 %s 变更节点: %s -> %s\n", gateway.Macaddr, oldConfig.Macaddr, newConfig.Macaddr)

				configs2Update = append(configs2Update, model.ConfigUpdate{
					Id:       oldConfig.Id,
					Macaddr:  u.P(oldConfig.Macaddr),
					IsChange: u.P(0),
				})

				newConfigs[gateway][iCity] = ConfigInfo{
					Type: 1, // 更新
					Item: jd.EdgeInfo{
						Mac:  oldConfig.Macaddr,
						City: city.Hash,
					},
				}
			} else {
				fmt.Printf("\t网关 %s 新增节点: %s\n", gateway.Macaddr, newConfig.Macaddr)

				configs2Create = append(configs2Create, model.Config{
					Cityhash:   city.Hash,
					CityLabel:  city.Label,
					GatewayMac: gateway.Macaddr,
					Macaddr:    newConfig.Macaddr,
					Table:      strconv.Itoa(iCity + 1),
					User:       fmt.Sprintf("jdzz%ddt%d", iGateway+1, iCity+1),
					Network:    fmt.Sprintf("172.30.168.%d", iCity+2),
					InnerIp:    fmt.Sprintf("172.16.%d.%d", iGateway+1, iCity+2),
				})

				newConfigs[gateway][iCity] = ConfigInfo{
					Type: 2, // 新增
					Item: jd.EdgeInfo{
						Mac:  oldConfig.Macaddr,
						City: city.Hash,
					},
				}
			}
		}

		err = CreateConfigs(tx, configs2Create)
		if err != nil {
			return fmt.Errorf("创建新配置失败：%w", err)
		}
		err = UpdateConfigs(tx, configs2Update)
		if err != nil {
			return fmt.Errorf("更新配置失败：%w", err)
		}
		err = AppendCityOffset(tx, city.Id, offset+count)
		if err != nil {
			return fmt.Errorf("更新城市 %s 的偏移量失败：%w", city.Name, err)
		}
	}

	// 提交所有网关配置到云端
	for gateway, infos := range newConfigs {
		change := 0
		setup := 0
		edges := make([]jd.EdgeInfo, len(infos))
		for i, info := range infos {
			edges[i] = info.Item
			switch info.Type {
			case 1:
				change++
			case 2:
				setup++
			}
		}
		fmt.Printf("提交网关 %s 配置: %d 变更, %d 新增\n", gateway.Macaddr, change, setup)

		// 提交配置到云端：配置版本 gateway.ConfigVersion
		if arg.Mock {
			fmt.Printf("[MOCK] 配置网关 %s:\n%v\n", gateway.Macaddr, edges)
		} else {
			err := jd.GatewayConfigSet(gateway.ConfigVersion, gateway.Macaddr, edges)
			if err != nil {
				return fmt.Errorf("配置网关 %s 失败：%w", gateway.Macaddr, err)
			}
		}

		// 更新配置版本
		err = AppendGatewayConfigVersion(tx, gateway.Id)
		if err != nil {
			return fmt.Errorf("更新网关 %s 配置版本失败：%w", gateway.Macaddr, err)
		}
	}

	return nil
}
