package comsvc

import (
	"context"
	"errors"

	"github.com/duke-git/lancet/v2/slice"
	"gorm.io/gorm"

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

func ListIdentRoleBindings(ctx context.Context, identType entity.TargetType, identID, orgID uint64, scopes []entity.ScopeType, status []string) ([]entity.RoleBinding, error) {
	tilog.Info(ctx, "ListIdentRoleBindings param", zap.Any("ident_type", identType), zap.Any("ident_id", identID), zap.Any("org_id", orgID), zap.Any("scopes", scopes))
	var rBindings []entity.RoleBinding

	if slice.Contain(scopes, entity.RoleBindingScopeOrg) {
		// org binding
		oBindings, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, database.DBConn.DB, daorole.RoleBindingFilter{
			TargetType: []entity.TargetType{identType},
			TargetIDs:  []entity.ID{entity.ID(identID)},
			ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeOrg},
			ScopeIDs:   []entity.ID{entity.ID(orgID)},
			Status:     status,
		})
		if err != nil {
			tilog.Error(ctx, "ListIdentRoleBindings list org role binding error", zap.Any("err", err))
			return nil, err
		}
		if len(oBindings) == 0 {
			tilog.Info(ctx, "ListIdentRoleBindings not found org role binding", zap.Any("ident_type", identType), zap.Any("ident_id", identID), zap.Any("org_id", orgID))
			return rBindings, nil
		}
		rBindings = append(rBindings, oBindings[0])
	}

	if slice.Contain(scopes, entity.RoleBindingScopeProject) {
		// project binding
		aps, err := dao.GetDaoHub().Project.ListActiveProjectsByOrgID(ctx, database.DBConn.DB, orgID)
		if err != nil {
			tilog.Error(ctx, "ListIdentRoleBindings list org active projects error", zap.Any("err", err))
			return nil, errs.NewInternalUnknown("ListIdentRoleBindings list org active projects internal error", err)
		}
		var aPids []entity.ID
		for _, ap := range aps {
			aPids = append(aPids, ap.ID)
		}
		pBindings, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, database.DBConn.DB, daorole.RoleBindingFilter{
			TargetType: []entity.TargetType{identType},
			TargetIDs:  []entity.ID{entity.ID(identID)},
			ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeProject},
			ScopeIDs:   aPids,
			Status:     status,
		})
		if err != nil {
			tilog.Error(ctx, "ListIdentRoleBindings list project role binding error", zap.Any("err", err))
			return nil, err
		}
		if len(pBindings) > 0 {
			rBindings = append(rBindings, pBindings...)
		}
	}

	if slice.Contain(scopes, entity.RoleBindingScopeTiDB) {
		// cluster binding
		cBindings, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, database.DBConn.DB, daorole.RoleBindingFilter{
			TargetType: []entity.TargetType{identType},
			TargetIDs:  []entity.ID{entity.ID(identID)},
			ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeTiDB},
			Status:     status,
		})
		if err != nil {
			tilog.Error(ctx, "ListIdentRoleBindings list tidb role bindings error", zap.Any("err", err))
			return nil, errs.NewInternalUnknown("list tidb role bindings internal error", err)
		}

		if len(cBindings) > 0 {
			hasRoleAllClusterIDs := make([]uint64, 0, len(cBindings))
			seen := make(map[uint64]struct{}, len(cBindings))
			for _, rb := range cBindings {
				scopeID := rb.ScopeID.Uint64()
				if _, ok := seen[scopeID]; ok {
					continue
				}
				seen[scopeID] = struct{}{}
				hasRoleAllClusterIDs = append(hasRoleAllClusterIDs, scopeID)
			}
			cidsInO, err := dao.GetDaoHub().ResourceRelation.GetResourceIDsByParentAndResourceIDs(ctx, database.DBConn.DB, entity.RoleBindingScopeOrg.String(), orgID, entity.RoleBindingScopeTiDB.String(), hasRoleAllClusterIDs)
			if err != nil {
				tilog.Error(ctx, "ListIdentRoleBindings get cluster relations error", zap.Any("err", err))
				return nil, errs.NewInternalUnknown("get cluster relations internal error", err)
			}

			for _, cb := range cBindings {
				if slice.Contain(cidsInO, cb.ScopeID.Uint64()) {
					rBindings = append(rBindings, cb)
				}
			}
		}

	}

	tilog.Info(ctx, "ListIdentRoleBindings result", zap.Any("ident_type", identType), zap.Any("ident_id", identID), zap.Any("bindings", rBindings))

	return rBindings, nil

}

func GetUserAndScopeRoleBinding(ctx context.Context, email string, orgId, projectId uint64, status []string) (user *entity.User, oBinding *entity.RoleBinding, pBinding *entity.RoleBinding, err error) {
	if email == "" || orgId == 0 {
		return nil, nil, nil, errs.NewBadRequest("param invalid", nil)
	}

	user, err = dao.GetDaoHub().User.GetUserByEmail(ctx, database.DBConn.DB, email)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil, nil, nil
		} else {
			tilog.Error(ctx, "InviteOneUser GetUserByEmail error", zap.Any("err", err))
			return nil, nil, nil, errs.NewInternalUnknown("GetUserByEmail error", err)
		}
	}
	if user == nil {
		return nil, nil, nil, nil
	}

	oBindings, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, database.DBConn.DB, daorole.RoleBindingFilter{
		TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
		TargetIDs:  []entity.ID{user.ID},
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeOrg},
		ScopeIDs:   []entity.ID{entity.ID(orgId)},
		Status:     status,
	})
	if err != nil {
		return nil, nil, nil, errs.NewInternalUnknown("list org role binding error", err)
	}
	if len(oBindings) == 0 {
		return user, nil, nil, nil
	}
	oBinding = &oBindings[0]

	if projectId != 0 {
		pBindings, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, database.DBConn.DB, daorole.RoleBindingFilter{
			TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
			TargetIDs:  []entity.ID{user.ID},
			ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeProject},
			ScopeIDs:   []entity.ID{entity.ID(projectId)},
			Status:     status,
		})
		if err != nil {
			return nil, nil, nil, errs.NewInternalUnknown("list project role binding error", err)
		}
		if len(pBindings) > 0 {
			pBinding = &pBindings[0]
		}
	}

	return user, oBinding, pBinding, nil
}
