package tidbcloud

import (
	"fmt"
	"log"
	"net/http"
	"reflect"
	"sync"
	"time"

	"github.com/pkg/errors"
	"gorm.io/gorm"

	"github.com/pingcap/crm-bot/model"
	"github.com/pingcap/crm-bot/pkg/util"
)

func GetToken() string {
	req, _ := util.HttpRequest(http.MethodPost, "https://tidb.auth0.com/oauth/token",
		map[string]string{
			"Content-Type": "application/json",
		}, nil, nil)
	resp := map[string]interface{}{}
	err := util.Do(req, &resp)
	if err != nil {
		return ""
	}

	return resp["access_token"].(string)
}

func LoadCloudTenant(token string) ([]model.CloudTenant, error) {
	page := int32(0)
	perPage := int32(100)
	totalCt := int32(10000)
	tanents := []model.CloudTenant{}
	concur := util.NewConcurency(5)
	var lock sync.Mutex
	for ; page*perPage <= totalCt; page++ {
		p := page
		concur.Run(func() {
			resp := model.RespCloudTenant{}
			tryCt := 0
		tanentretry:
			req, _ := util.HttpRequest(http.MethodGet, "https://us-west-2.prod.aws.tidbcloud.com/api/v1/admin/orgs",
				map[string]string{"Authorization": fmt.Sprintf("Bearer %s", token)},
				map[string]string{"page": fmt.Sprintf("%d", p+1),
					"per_page": fmt.Sprintf("%d", perPage)}, nil)

			err := util.Do(req, &resp)
			if err != nil {
				log.Println("[error] ", err, req)
				if tryCt < 3 {
					tryCt++
					goto tanentretry
				}
				return
			}

			totalCt = int32(resp.Total)
			lock.Lock()
			defer lock.Unlock()
			tanents = append(tanents, resp.Items...)
		})
	}
	concur.Wait()

	return tanents, nil
}

func LoadTiDBCloudInfo(gdb *gorm.DB) error {
	token := GetToken()
	if token == "" {
		return errors.New("token is null")
	}

	tenants, err := LoadCloudTenant(token)
	if err != nil {
		return err
	}

	gdb.Where("1=1").Delete(&model.CloudTenant{})
	err = saveArrayByStep(gdb, tenants, 500, "save CloudTenant err")
	if err != nil {
		return err
	}

	clusters, err := loadTenantCluster(tenants, token)
	if err != nil {
		return err
	}
	gdb.Where("1=1").Delete(&model.CloudTenantCluster{})
	err = saveArrayByStep(gdb, clusters, 500, "save CloudTenantCluster err")
	if err != nil {
		return err
	}
	log.Println("CloudTenantCluster done", len(clusters))

	clusterComponents := []model.CloudTenantClusterComponent{}
	for _, c := range clusters {
		for _, comp := range *c.Components {
			clusterComponents = append(clusterComponents, comp)
		}
	}
	gdb.Where("1=1").Delete(&model.CloudTenantClusterComponent{})
	err = saveArrayByStep(gdb, clusterComponents, 500, "save CloudTenantClusterComponent err")
	log.Println("CloudTenantClusterComponent done")
	return err
}

func loadTenantCluster(tanents []model.CloudTenant, token string) ([]model.CloudTenantCluster, error) {
	clusters := []model.CloudTenantCluster{}
	concur := util.NewConcurency(7)
	var lock sync.Mutex
	for i := range tanents {
		tanent := tanents[i]
		//if tanent.Status != "active" {
		//	continue
		//}
		concur.Run(func() {
			resp := model.RespCloudTenantCluster{}
			retry := 0
		do_retry:
			req, _ := util.HttpRequest(http.MethodGet,
				fmt.Sprintf("https://us-west-2.prod.aws.tidbcloud.com/api/v1/admin/orgs/%s/clusters", tanent.ID),
				map[string]string{"Authorization": fmt.Sprintf("Bearer %s", token)},
				nil, nil)

			err := util.Do(req, &resp)
			if err != nil {
				log.Println("[error] ", err, req)
				retry++
				if retry < 3 {
					goto do_retry
				}
				return
			}

			for i := range resp.Items {
				resp.Items[i].TenantId = tanent.ID
				for k := range *resp.Items[i].Components {
					comp := (*resp.Items[i].Components)[k]
					comp.ComponentName = k
					comp.ClusterId = resp.Items[i].ID
					comp.NodeInfo, _ = comp.NodeProfile.Json2String()
					(*resp.Items[i].Components)[k] = comp
				}
			}

			time.Sleep(time.Second)
			lock.Lock()
			defer lock.Unlock()
			clusters = append(clusters, resp.Items...)
		})
	}
	concur.Wait()

	return clusters, nil
}

func saveArrayByStep(gdb *gorm.DB, arrayData interface{}, step int, errmsg string) error {
	arr := reflect.ValueOf(arrayData)
	totalLen := arr.Len()
	for idx := 0; idx < totalLen; idx += step {
		end := totalLen
		if idx+step < totalLen {
			end = idx + step
		}
		err := errors.Wrap(gdb.Save(arr.Slice(idx, end).Interface()).Error, errmsg)
		if err != nil {
			return err
		}
	}

	return nil
}
