package authcenter

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/tidbcloud/account/commons/database"

	"github.com/google/uuid"

	"github.com/tidbcloud/account/internel/dao"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/internel/dao/org"
	"github.com/tidbcloud/account/thirdparty/infra"
	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type SyncCmClusterUserSvcIface interface {
	SyncCLusterUser(ctx context.Context, orgID uint64) error
}

var (
	syncCmClusterUserSvcInitOnce sync.Once
	syncCmClusterUserSvcInstance SyncCmClusterUserSvcIface
)

func GetSyncCmClusterUserSvc() (SyncCmClusterUserSvcIface, error) {
	syncCmClusterUserSvcInitOnce.Do(func() {
		syncCmClusterUserSvcInstance = NewSyncCmClusterUserSvc(database.DBConn.DB, dao.GetDaoHub())
	})
	return syncCmClusterUserSvcInstance, nil
}

type SyncCmClusterUserSvc struct {
	db  *gorm.DB
	hub *dao.DaoHub
}

func NewSyncCmClusterUserSvc(db *gorm.DB, hub *dao.DaoHub) SyncCmClusterUserSvcIface {
	return &SyncCmClusterUserSvc{
		db:  db,
		hub: hub,
	}
}

func (s *SyncCmClusterUserSvc) SyncCLusterUser(ctx context.Context, orgID uint64) error {
	traceid := uuid.New().String()
	tilog.Info(ctx, "SyncOrgCLusterUser start", zap.Any("trace_id", traceid))
	if orgID != 0 {
		s.syncOrgClusterUsers(ctx, orgID, traceid)
		return nil
	}

	var offset = 0
	param := org.ListOrgsParam{
		SortBy: "id",
		Order:  "asc",
		Limit:  10,
	}
	for {
		tilog.Info(ctx, fmt.Sprintf("SyncOrgCLusterUser ListOrgs offset %d", offset), zap.Any("trace_id", traceid))
		param.Offset = offset
		orgs, err := s.hub.Org.ListOrgs(ctx, s.db, param)
		if err != nil {
			tilog.Error(ctx, "SyncOrgCLusterUser ListOrgs error", zap.Any("trace_id", traceid), zap.Any("param", param), zap.Any("err", err))
			return err
		}
		if len(orgs.List) == 0 {
			break
		}
		for _, ov := range orgs.List {
			s.syncOrgClusterUsers(ctx, ov.ID.Uint64(), traceid)
		}
		l := len(orgs.List)
		offset += l
	}

	tilog.Info(ctx, "SyncOrgCLusterUser over", zap.Any("trace_id", traceid))
	return nil
}

func (s *SyncCmClusterUserSvc) syncOrgClusterUsers(ctx context.Context, orgID uint64, traceid string) {
	s.doSyncClusterUsers(ctx, orgID, true, traceid)
	s.doSyncClusterUsers(ctx, orgID, false, traceid)
}

func (s *SyncCmClusterUserSvc) doSyncClusterUsers(ctx context.Context, orgID uint64, isDevTier bool, traceid string) {
	tilog.Info(ctx, fmt.Sprintf("SyncOrgCLusterUser start, orgID: %d, isDevTier: %t", orgID, isDevTier), zap.Any("trace_id", traceid))

	client := infra.GetCoreSvcClient()

	req := core.ListClustersReq{
		Filter: &core.ClusterFilter{
			TenantIds: []uint64{orgID},
			IsDevTier: &isDevTier,
		},
	}

	rsp, err := client.ListClusters(ctx, &req)
	if err != nil {
		tilog.Warn(ctx, fmt.Sprintf("SyncOrgCLusterUser ListClusters error, orgID: %d, isDevTier: %t", orgID, isDevTier), zap.Any("trace_id", traceid), zap.Any("err", err))
		return
	}
	if rsp.BaseResp.ErrCode != 0 {
		tilog.Warn(ctx, fmt.Sprintf("SyncOrgCLusterUser ListClusters fail, orgID: %d, isDevTier: %t", orgID, isDevTier), zap.Any("trace_id", traceid), zap.Any("rsp", rsp))
		return
	}
	tilog.Info(ctx, fmt.Sprintf("SyncOrgCLusterUser find cluster count, orgID: %d, isDevTier: %t, clusterCount: %d", orgID, isDevTier, len(rsp.OrderedClusterIds)), zap.Any("trace_id", traceid))
	for _, clusterID := range rsp.OrderedClusterIds {
		cluster := rsp.Clusters[clusterID]
		infoReq := core.GetClusterConfidentialInfoReq{
			ClusterId: clusterID,
		}
		infoRsp, err := infra.GetHandler().GetClusterConfidentialInfo(ctx, &infoReq)
		if err != nil {
			tilog.Warn(ctx, fmt.Sprintf("SyncOrgCLusterUser GetClusterConfidentialInfo error, orgID: %d, isDevTier: %t, clusterID: %d", orgID, isDevTier, clusterID), zap.Any("trace_id", traceid), zap.Any("err", err))
			continue
		}
		if infoRsp.BaseResp.ErrCode != 0 {
			tilog.Warn(ctx, fmt.Sprintf("SyncOrgCLusterUser GetClusterConfidentialInfo fail, orgID: %d, isDevTier: %t, clusterID: %d", orgID, isDevTier, clusterID), zap.Any("trace_id", traceid), zap.Any("infoRsp", infoRsp))
			continue
		}
		if infoRsp.Info.EncryptedCloudAdminPasswd == "" {
			tilog.Warn(ctx, fmt.Sprintf("SyncOrgCLusterUser EncryptedCloudAdminPasswd is blank, orgID: %d, isDevTier: %t, clusterID: %d", orgID, isDevTier, clusterID), zap.Any("trace_id", traceid), zap.Any("infoRsp", infoRsp))
			continue
		}
		dbClusterUser, err := s.hub.ClusterUser.GetClusterUserByClusterIDUsername(ctx, s.db, clusterID, CloudAdminUsername)
		if err != nil {
			tilog.Warn(ctx, fmt.Sprintf("SyncOrgCLusterUser GetClusterUserByClusterIDUsername error, orgID: %d, isDevTier: %t, clusterID: %d", orgID, isDevTier, clusterID), zap.Any("trace_id", traceid), zap.Any("err", err))
			continue
		}
		if dbClusterUser != nil {
			tilog.Info(ctx, fmt.Sprintf("SyncOrgCLusterUser cluster already synced, orgID: %d, isDevTier: %t, clusterID: %d", orgID, isDevTier, clusterID), zap.Any("trace_id", traceid), zap.Any("dbClusterUser", dbClusterUser))
			continue
		}

		var serverlessToken = ""
		if cluster.ServerlessToken != nil {
			serverlessToken = *cluster.ServerlessToken
		}

		_, err = createCmCloudAdminClusterUser(ctx, s.db, s.hub, clusterID, serverlessToken, infoRsp.Info.EncryptedCloudAdminPasswd)
		if err != nil {
			tilog.Warn(ctx, fmt.Sprintf("SyncOrgCLusterUser CreateClusterUser error, orgID: %d, isDevTier: %t, clusterID: %d", orgID, isDevTier, clusterID), zap.Any("trace_id", traceid), zap.Any("err", err))
		} else {
			tilog.Info(ctx, fmt.Sprintf("SyncOrgCLusterUser cluster sync success, orgID: %d, isDevTier: %t, clusterID: %d", orgID, isDevTier, clusterID), zap.Any("trace_id", traceid))
		}

		time.Sleep(time.Second)
	}

	tilog.Info(ctx, fmt.Sprintf("SyncOrgCLusterUser end, orgID: %d, isDevTier: %t", orgID, isDevTier), zap.Any("trace_id", traceid))

}

func createCmCloudAdminClusterUser(ctx context.Context, db *gorm.DB, hub *dao.DaoHub, clusterId uint64, serverlessToken string, encryptPassword string) (*entity.ClusterUser, error) {
	clusterUser := &entity.ClusterUser{
		ClusterID:       entity.ID(clusterId),
		ServerlessToken: serverlessToken,
		Username:        CloudAdminUsername,
		Credential:      encryptPassword,
		CredentialType:  entity.CredentialTypePassword,
		Description:     "",
		Status:          entity.Active,
		TargetType:      entity.ClusterUserTargetBuiltinUser,
		UserEmail:       "",
	}
	createdClusterUser, err := hub.ClusterUser.CreateClusterUser(ctx, db, clusterUser)
	return createdClusterUser, err
}
