package internal

import (
	"encoding/json"
	"fmt"
	"os"
	"slices"
	"strconv"
	"strings"

	"github.com/juju/errors"
	log "github.com/sirupsen/logrus"
	"github.com/tidbcloud/auto-deploy/internal/constants"
	ds_client "github.com/tidbcloud/auto-deploy/internal/dataservice-client"
	global_client "github.com/tidbcloud/auto-deploy/internal/global-client"
	"github.com/tidbcloud/auto-deploy/internal/types"
	"github.com/tidbcloud/auto-deploy/internal/utils"
)

type RegisterDefaultSkuArgs struct {
	Env                string
	Provider           string
	Region             string
	SpusCollectionName string
	SkuCollectionName  string
	Recreate           bool
}

type sku struct {
	SkuGroupId  int
	GroupLabels map[string]string
}

type spuMappingItem struct {
	TierUnitKey string
}

var DEFAULT_BILLING_SKU_URL = "/api/v1beta/app/dataapp-WObJIvhw/endpoint/%s/defaultBillingSKUs"
var DEFAULT_MGMT_SKU_URL = "/api/v1beta/app/dataapp-WObJIvhw/endpoint/%s/defaultMgmtSKUs"

func loadBillingDefaultSku(provider, env, skuCollectionName string) ([]sku, error) {
	client := ds_client.NewDataServiceClient(
		os.Getenv(constants.DATA_SERVICE_PUBLIC_KEY_ENV),
		os.Getenv(constants.DATA_SERVICE_PRIVATE_KEY_ENV),
		ds_client.DEFAULT_TIMEOUT,
		log.IsLevelEnabled(log.DebugLevel),
	)
	resp := ds_client.Response{}
	err := client.Get(fmt.Sprintf(DEFAULT_BILLING_SKU_URL, env), map[string]string{
		"provider":        provider,
		"collection_name": fmt.Sprintf(`"%s"`, skuCollectionName),
	}, &resp)
	if err != nil {
		return nil, err
	}
	if resp.Data.Result.Code != 200 {
		return nil, errors.Errorf("Get an error message %s", resp.Data.Result.Message)
	}

	skus := []sku{}
	for _, row := range resp.Data.Rows {
		group_id, err := strconv.Atoi(row["sku_group_id"])
		if err != nil {
			return nil, errors.Annotatef(err, "Failed to parse sku_group_id %s", row["sku_group_id"])
		}
		group_labels := map[string]string{}
		err = json.Unmarshal([]byte(row["group_labels"]), &group_labels)
		if err != nil {
			return nil, errors.Annotatef(err, "Failed to parse group_labels %s", row["group_labels"])
		}

		skus = append(skus, sku{
			SkuGroupId:  group_id,
			GroupLabels: group_labels,
		})
	}
	return skus, nil
}

func getSkuKey(provider, region, pattern string, labels map[string]string) string {
	labels = utils.Merge(labels, map[string]string{
		"provider": provider,
		"region":   region,
	})
	for key, val := range labels {
		pattern = strings.ReplaceAll(pattern, "{"+key+"}", val)
	}
	return pattern
}

func RegisterDefaultSku(args RegisterDefaultSkuArgs) error {
	err := registerBillingDefaultSku(args)
	if err != nil {
		return err
	}
	return registerMgmtDefaultSpus(args)
}

func registerBillingDefaultSku(args RegisterDefaultSkuArgs) error {
	skus, err := loadBillingDefaultSku(args.Provider, args.Env, args.SkuCollectionName)
	if err != nil {
		return err
	}

	authKey := os.Getenv(constants.GLOBAL_AUTH_KEY_ENV)
	client := global_client.NewGlobalClient(args.Env, authKey, global_client.DEFAULT_TIMEOUT, true)

	for _, sku := range skus {
		skuGroup, err := client.GetSkuGroupById(sku.SkuGroupId)
		if err != nil {
			return err
		}

		key := getSkuKey(args.Provider, args.Region, skuGroup.SkuKeyPattern, sku.GroupLabels)
		_, err = client.GetSku(key)
		if err == nil {
			log.WithField("sku-key", key).Info("Sku already exists")
			if !args.Recreate {
				continue
			}

			log.WithField("sku-key", key).Info("Sku already exists, recreate is true, delete it")
			err = client.DeleteSku(key)
			if err != nil {
				log.WithField("sku-key", key).WithError(err).Error("Failed to delete sku")
				return err
			}

			_, err = client.NewSku(global_client.AddSkuRequest{
				GroupId:     skuGroup.GroupId,
				Provider:    args.Provider,
				Region:      args.Region,
				GroupLabels: sku.GroupLabels,
			})
		} else if errResp, ok := err.(*global_client.BillingErrorResponse); ok && errResp.Code == "ObjectNotExists" { // not found
			_, err = client.NewSku(global_client.AddSkuRequest{
				GroupId:     skuGroup.GroupId,
				Provider:    args.Provider,
				Region:      args.Region,
				GroupLabels: sku.GroupLabels,
			})
		}
		if err != nil {
			log.WithField("sku-key", key).WithError(err).Error("Failed to register sku")
			return err
		}
	}

	return nil
}

func loadMgmtDefaultSpu(provider, env, collectionName string) (map[string][]spuMappingItem, error) {
	client := ds_client.NewDataServiceClient(
		os.Getenv(constants.DATA_SERVICE_PUBLIC_KEY_ENV),
		os.Getenv(constants.DATA_SERVICE_PRIVATE_KEY_ENV),
		ds_client.DEFAULT_TIMEOUT,
		log.IsLevelEnabled(log.DebugLevel),
	)
	resp := ds_client.Response{}
	err := client.Get(fmt.Sprintf(DEFAULT_MGMT_SKU_URL, env), map[string]string{
		"provider":        provider,
		"collection_name": collectionName,
	}, &resp)
	if err != nil {
		return nil, err
	}
	if resp.Data.Result.Code != 200 {
		return nil, errors.Errorf("Get an error message %s", resp.Data.Result.Message)
	}

	spuMap := make(map[string][]spuMappingItem)
	for _, row := range resp.Data.Rows {
		spuId := row["spu_id"]
		tierUnitKey := row["tier_unit_key"]
		if spuId == "" {
			continue
		}

		spuMap[spuId] = append(spuMap[spuId], spuMappingItem{
			TierUnitKey: tierUnitKey,
		})
	}
	return spuMap, nil
}

func addRegionsIfNotExist(spu *global_client.Spu, provider, region string, mappingItems []spuMappingItem) (bool, error) {
	updated := false
	for _, item := range mappingItems {
		found := false
		for i := range spu.Mappings {
			if spu.Mappings[i].TierUnitKey == item.TierUnitKey {
				found = true
				if spu.Mappings[i].CloudProvider != provider {
					log.WithFields(log.Fields{
						"spu-id":            spu.Id,
						"spu-name":          spu.Name,
						"expected-provider": spu.Mappings[i].CloudProvider,
						"actual-provider":   provider,
						"tier-unit-key":     item.TierUnitKey,
					}).Error("Cloud provider of tier unit key mismatched")
					return false, errors.Errorf("Cloud provider of tier unit key expect %s but %s", spu.Mappings[i].CloudProvider, provider)
				}
				if slices.Contains(spu.Mappings[i].PhysicalRegions, region) {
					log.WithFields(log.Fields{
						"spu-id":        spu.Id,
						"spu-name":      spu.Name,
						"provider":      provider,
						"tier-unit-key": item.TierUnitKey,
						"region":        region,
					}).Debug("sku mapping exists")
					continue
				}
				spu.Mappings[i].PhysicalRegions = append(spu.Mappings[i].PhysicalRegions, region)
				updated = true
				log.WithFields(log.Fields{
					"spu-id":        spu.Id,
					"spu-name":      spu.Name,
					"provider":      provider,
					"tier-unit-key": item.TierUnitKey,
					"region":        region,
				}).Info("add new region to sku mapping")
			}
		}
		if !found {
			log.WithFields(log.Fields{
				"spu-id":        spu.Id,
				"spu-name":      spu.Name,
				"provider":      provider,
				"tier-unit-key": item.TierUnitKey,
			}).Error("SKU has not configed")
			return false, errors.NotValidf("SPU %s with key %s", spu.Id, item.TierUnitKey)
		}
	}
	return updated, nil
}

func registerMgmtDefaultSpus(args RegisterDefaultSkuArgs) error {
	spus, err := loadMgmtDefaultSpu(args.Provider, args.Env, args.SpusCollectionName)
	if err != nil {
		return err
	}

	authKey := os.Getenv(constants.GLOBAL_AUTH_KEY_ENV)
	client := global_client.NewGlobalClient(args.Env, authKey, global_client.DEFAULT_TIMEOUT, log.IsLevelEnabled(log.DebugLevel))
	for id, tierUnitKeys := range spus {
		spu, err := client.GetMgmtSpuById(id)
		if err != nil {
			return err
		}

		regionId := global_client.GetRegionId(args.Region, types.CloudProviderType(args.Provider))
		updated, err := addRegionsIfNotExist(spu, strings.ToUpper(args.Provider), regionId, tierUnitKeys)
		if err != nil {
			return err
		}
		if updated {
			_, err = client.PatchMgmtSpu(*spu)
			if err != nil {
				return err
			}
			log.WithFields(log.Fields{
				"spu-id":   id,
				"spu-name": spu.Name,
				"provider": args.Provider,
				"region":   regionId,
			}).Info("Updated SPU")
		}
	}
	return nil
}
