package comsvc

import (
	"context"
	"errors"
	"fmt"

	"github.com/duke-git/lancet/slice"
	"github.com/tidbcloud/account/commons/database"
	"github.com/tidbcloud/account/internel/dao"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/tiinf/errs"
	"gorm.io/gorm"
)

const (
	allowAssignClusterMaxCount = 150
)

func CheckAssignScopeAndRoleValid(ctx context.Context, orgId uint64, oRole *string, pRoles, cRoles map[uint64]string) error {
	// check org
	org, err := dao.GetDaoHub().Org.GetOrgByOrgID(ctx, database.DBConn.DB, orgId)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			org = nil
		} else {
			return errs.NewInternalUnknown("get org by id internal error", err)
		}
	}
	if org == nil || org.Status != entity.TenantStatusActive {
		return errs.NewBadRequest("org status is not active", nil)
	}
	if oRole != nil {
		var ok bool
		if IsPartnerOrg(org.OrgTag) {
			_, ok = entity.ValidUserPartnerRbacRoles[*oRole]
		} else {
			_, ok = entity.ValidUserOrgRbacRoles[*oRole]
			if !ok && *oRole == entity.RoleBearerTokenApiKeyScimOwner {
				ok = true
			}
		}
		if !ok {
			return errs.NewBadRequest("org role is invalid", nil)
		}
	}
	// check project
	if len(pRoles) > 0 {
		aps, err := dao.GetDaoHub().Project.ListActiveProjectsByOrgID(ctx, database.DBConn.DB, orgId)
		if err != nil {
			return errs.NewInternalUnknown("list org active projects internal error", err)
		}
		pId2EntMap := map[uint64]entity.Project{}
		for _, ap := range aps {
			pId2EntMap[ap.ID.Uint64()] = ap
		}
		for pid, prole := range pRoles {
			pEnt, ok := pId2EntMap[pid]
			if !ok {
				return errs.NewBadRequest(fmt.Sprintf("project %d is not this org's active project", pid), nil)
			}
			if pEnt.Type == entity.ProjectTypeTiDBXVirtual {
				return errs.NewBadRequest(fmt.Sprintf("project %d is tidbx virtual project, can not assign role", pid), nil)
			}
			if _, ok := entity.ValidUserProjectRbacRoles[prole]; !ok {
				return errs.NewBadRequest(fmt.Sprintf("project %d role %s is invalid", pid, prole), nil)
			}
		}
	}
	// check cluster
	if len(cRoles) > 0 {
		if len(cRoles) > allowAssignClusterMaxCount {
			return errs.NewInternalUnknown("assign cluster count exceeded", err)
		}
		var toAssignClusterIds []uint64
		for cid := range cRoles {
			toAssignClusterIds = append(toAssignClusterIds, cid)
		}
		cidsInOrg, err := dao.GetDaoHub().ResourceRelation.GetResourceIDsByParentAndResourceIDs(ctx, database.DBConn.DB, entity.RoleBindingScopeOrg.String(), orgId, entity.RoleBindingScopeTiDB.String(), toAssignClusterIds)
		if err != nil {
			return errs.NewInternalUnknown("get cluster relations internal error", err)
		}
		for cid, crole := range cRoles {
			if !slice.Contain(cidsInOrg, cid) {
				return errs.NewBadRequest(fmt.Sprintf("cluster %d is not this org's cluster", cid), nil)
			}
			if _, ok := entity.ValidClusterRbacRoles[crole]; !ok {
				return errs.NewBadRequest(fmt.Sprintf("cluster %d role %s is invalid", cid, crole), nil)
			}
		}
	}

	return nil
}

func IsPartnerOrg(orgTag string) bool {
	switch orgTag {
	case string(entity.OrgTagMsp), string(entity.OrgTagAwsCppo), string(entity.OrgTagGcpMCPOReseller), string(entity.OrgTagResalePartner), string(entity.OrgTagAlicloud):
		return true
	}
	return false
}
