package monitor

import (
	"context"
	"fmt"
	"time"

	"github.com/google/uuid"
	"github.com/tidbcloud/tiinf/errs"

	"github.com/tidbcloud/account/thirdparty/mgmt"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"

	"github.com/tidbcloud/account/commons/database"
	"github.com/tidbcloud/account/internel/dao"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/internel/dao/org"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

func SyncClusterRelations(ctx context.Context, orgId uint64) error {
	tilog.Info(ctx, "sync cluster relations start")
	if orgId != 0 {
		syncOrgClusters(ctx, orgId)
		return nil
	}

	var offset = 0
	param := org.ListOrgsParam{
		SortBy: "id",
		Order:  "asc",
		Limit:  10,
	}
	for {
		tilog.Info(ctx, fmt.Sprintf("sync cluster relations list orgs offset %d", offset))
		param.Offset = offset
		orgs, err := dao.GetDaoHub().Org.ListOrgs(ctx, database.DBConn.DB, param)
		if err != nil {
			tilog.Error(ctx, "sync cluster relations list orgs error", zap.Any("param", param), zap.Any("err", err))
			return err
		}
		if len(orgs.List) == 0 {
			break
		}
		for _, ov := range orgs.List {
			syncOrgClusters(ctx, ov.ID.Uint64())
		}
		l := len(orgs.List)
		offset += l
	}

	tilog.Info(ctx, "sync cluster relations over")
	return nil
}

type cpoGroup struct {
	ClusterID uint64 `json:"cluster_id"`
	ProjectID uint64 `json:"project_id"`
	OrgID     uint64 `json:"org_id"`
}

func syncOrgClusters(ctx context.Context, orgId uint64) {
	traceid := uuid.New().String()
	tilog.Info(ctx, fmt.Sprintf("start sync org clusters, org_id: %d", orgId), zap.Any("org_trace", traceid))
	var page, pageSize uint32 = 0, 100
	var clusterCnt = 0
	for {
		page++
		tilog.Info(ctx, fmt.Sprintf("sync org clusters page_no: %d, org_id: %d", page, orgId), zap.Any("org_trace", traceid))
		pageClusters, err := listPageClusters(ctx, orgId, 0, page, pageSize)
		tilog.Info(ctx, fmt.Sprintf("sync org clusters page_no: %d, org_id: %d result", page, orgId), zap.Any("org_trace", traceid), zap.Any("clusters", pageClusters), zap.Any("err", err))
		if err != nil {
			tilog.Warn(ctx, fmt.Sprintf("list org: %d page clusters error", orgId), zap.Any("org_trace", traceid), zap.Any("page", page), zap.Any("page_size", pageSize), zap.Any("err", err))
			continue
		}
		var groups []cpoGroup
		for _, c := range pageClusters {
			g := cpoGroup{
				ClusterID: c.ClusterId,
				ProjectID: c.ProjectId,
				OrgID:     c.OrganizationId,
			}
			groups = append(groups, g)
		}
		tilog.Info(ctx, fmt.Sprintf("sync org clusters group result, page_no: %d, org_id: %d", page, orgId), zap.Any("org_trace", traceid), zap.Any("groups", groups))

		clusterCnt += len(pageClusters)
		if len(pageClusters) == 0 {
			tilog.Info(ctx, fmt.Sprintf("end sync org clusters, org_id: %d, cluster count: %d", orgId, clusterCnt), zap.Any("org_trace", traceid))
			if clusterCnt > 0 {
				tilog.Info(ctx, fmt.Sprintf("org has clusters, sync org %d cluster count: %d", orgId, clusterCnt), zap.Any("org_trace", traceid))
			}
			return
		}
		for _, c := range pageClusters {
			persistClusterRelation(ctx, c.ClusterId, c.DeployType, entity.RoleBindingScopeOrg.String(), c.OrganizationId, traceid)
			persistClusterRelation(ctx, c.ClusterId, c.DeployType, entity.RoleBindingScopeProject.String(), c.ProjectId, traceid)
		}
		time.Sleep(time.Millisecond * 100)
	}

}

func persistClusterRelation(ctx context.Context, cid uint64, deployType mgmtv1.DeployType, pType string, pid uint64, traceid string) {
	tilog.Info(ctx, fmt.Sprintf("start persist cluster relation, cluster_id: %d, parent_type: %s, parent_id: %d", cid, pType, pid), zap.Any("org_trace", traceid))
	//if deployType == mgmtv1.DeployType_DEPLOY_TYPE_DEDICATED {
	//	tilog.Info(ctx, fmt.Sprintf("cluster id dedicated, ignore, cluster_id: %d", cid), zap.Any("org_trace", traceid))
	//	return
	//}
	dbRef, err := dao.GetDaoHub().ResourceRelation.GetResourceRelation(ctx, database.DBConn.DB, entity.RoleBindingScopeTiDB.String(), cid, pType)
	if err != nil {
		tilog.Error(ctx, fmt.Sprintf("get cluster %s relation error, cluster_id: %d", pType, cid), zap.Any("org_trace", traceid), zap.Any("err", err))
		return
	}
	if dbRef == nil {
		err := dao.GetDaoHub().ResourceRelation.CreateResourceRelation(ctx, database.DBConn.DB, &entity.ResourceRelation{
			ResourceType: entity.RoleBindingScopeTiDB.String(),
			ResourceID:   cid,
			ParentType:   pType,
			ParentID:     pid,
		})
		if err != nil {
			tilog.Error(ctx, fmt.Sprintf("create cluster %s ref error, pid: %d, cluster_id: %d", pType, pid, cid), zap.Any("org_trace", traceid), zap.Any("err", err))
		} else {
			tilog.Info(ctx, fmt.Sprintf("create cluster %s ref success, pid: %d, cluster_id: %d", pType, pid, cid), zap.Any("org_trace", traceid))
		}
	} else {
		if dbRef.ParentID == pid {
			tilog.Info(ctx, fmt.Sprintf("cluster %s ref already exist, ignore, pid: %d, cluster_id: %d", pType, pid, cid), zap.Any("org_trace", traceid))
		} else {
			err := dao.GetDaoHub().ResourceRelation.UpsertResourceRelation(ctx, database.DBConn.DB, &entity.ResourceRelation{
				ResourceType: entity.RoleBindingScopeTiDB.String(),
				ResourceID:   cid,
				ParentType:   pType,
				ParentID:     pid,
			})
			if err != nil {
				tilog.Error(ctx, fmt.Sprintf("upsert cluster %s ref from %d to %d error, final pid: %d, cluster_id: %d", pType, dbRef.ParentID, pid, pid, cid), zap.Any("org_trace", traceid), zap.Any("err", err))
			} else {
				tilog.Info(ctx, fmt.Sprintf("upsert cluster %s ref from %d to %d error, final pid: %d, cluster_id: %d", pType, dbRef.ParentID, pid, pid, cid), zap.Any("org_trace", traceid))
			}
		}
	}
}

func listPageClusters(ctx context.Context, orgId, projectId uint64, page, pageSize uint32) ([]*mgmtv1.Cluster, error) {
	var deleted = false
	var tenantsIds []uint64
	if orgId != 0 {
		tenantsIds = append(tenantsIds, orgId)
	}
	var projectIds []uint64
	if projectId != 0 {
		projectIds = append(projectIds, projectId)
	}
	req := mgmtv1.ListClustersRequest{
		Filter: &mgmtv1.ClusterFilter{
			TenantIds:  tenantsIds,
			ProjectIds: projectIds,
			Deleted:    &deleted,
		},
		Page:     page,
		PageSize: pageSize,
	}
	// at most retry 3 times
	var clustersRsp *mgmtv1.ListClustersResponse
	var err error
	for i := 0; i <= 2; i++ {
		clustersRsp, err = mgmt.GeClient().ListClusters(ctx, &req)
		if err != nil {
			time.Sleep(time.Millisecond * 300)
			continue
		}
		if clustersRsp == nil {
			return nil, errs.NewInternalUnknown("list mgmt clusters return nil", nil)
		}
		return clustersRsp.Clusters, nil
	}
	return nil, err

}
