// Code generated by candi v1.8.18.

package usecase

import (
	"context"
	"errors"

	acldomain "monorepo/services/master-service/internal/modules/acl/domain"
	"monorepo/services/master-service/internal/modules/apps/domain"
	shareddomain "monorepo/services/master-service/pkg/shared/domain"
	"monorepo/services/master-service/pkg/shared/repository"

	"go.mongodb.org/mongo-driver/bson/primitive"
	"pkg.agungdp.dev/candi/candishared"
	"pkg.agungdp.dev/candi/codebase/factory/dependency"
	"pkg.agungdp.dev/candi/codebase/interfaces"
	"pkg.agungdp.dev/candi/tracer"
)

type appsUsecaseImpl struct {
	cache interfaces.Cache

	repoMongo *repository.RepoMongo
}

// NewAppsUsecase usecase impl constructor
func NewAppsUsecase(deps dependency.Dependency) AppsUsecase {
	return &appsUsecaseImpl{
		cache: deps.GetRedisPool().Cache(),

		repoMongo: repository.GetSharedRepoMongo(),
	}
}

func (uc *appsUsecaseImpl) FindAll(ctx context.Context, filter domain.FilterApps) (data []shareddomain.Apps, meta candishared.Meta, err error) {
	trace := tracer.StartTrace(ctx, "AppsUsecase:FindAll")
	defer trace.Finish()
	ctx = trace.Context()

	filter.CalculateOffset()

	count := make(chan int64)
	go func() {
		defer close(count)

		count <- uc.repoMongo.AppsRepo.Count(ctx, filter)
	}()

	data, err = uc.repoMongo.AppsRepo.FetchAll(ctx, filter)
	if err != nil {
		return data, meta, err
	}

	meta = candishared.NewMeta(filter.Page, filter.Limit, int(<-count))
	return
}

func (uc *appsUsecaseImpl) GetDetailApp(ctx context.Context, appID string) (data domain.AppDetail, err error) {
	trace := tracer.StartTrace(ctx, "AppsUsecase:GetDetailApp")
	defer trace.Finish()
	ctx = trace.Context()

	apps := shareddomain.Apps{Code: appID}
	if err := uc.repoMongo.AppsRepo.Find(ctx, &apps); err != nil {
		return data, errors.New("Data not found")
	}

	data.ID = apps.ID
	data.Code = apps.Code
	data.Name = apps.Name

	permissions, err := uc.repoMongo.PermissionRepo.FetchAll(ctx, domain.FilterPermission{
		Filter: candishared.Filter{ShowAll: true}, AppID: apps.ID,
	})
	permParentGroups := make(map[string][]shareddomain.Permission)
	for _, perm := range permissions {
		permParentGroups[perm.ParentID] = append(permParentGroups[perm.ParentID], perm)
		if perm.ParentID == "" {
			data.Permissions = append(data.Permissions, perm)
		}
	}

	var findAllChild func(parentID string) []shareddomain.Permission
	findAllChild = func(parentID string) (childs []shareddomain.Permission) {
		childs = permParentGroups[parentID]
		for i, c := range childs {
			c.AppsID = ""
			c.Childs = append(c.Childs, findAllChild(c.ID)...)
			childs[i] = c
		}
		return childs
	}

	for i, perm := range data.Permissions {
		perm.AppsID = ""
		perm.Childs = append(perm.Childs, findAllChild(perm.ID)...)
		data.Permissions[i] = perm
	}

	return
}

func (uc *appsUsecaseImpl) Save(ctx context.Context, data *shareddomain.Apps) (err error) {
	trace := tracer.StartTrace(ctx, "AppsUsecase:FindAll")
	defer trace.Finish()
	ctx = trace.Context()

	current := shareddomain.Apps{Code: data.Code}
	uc.repoMongo.AppsRepo.Find(ctx, &current)
	data.ID = current.ID

	return uc.repoMongo.AppsRepo.Save(ctx, data)
}

func (uc *appsUsecaseImpl) SaveAppsPermission(ctx context.Context, appID string, permissions domain.PermissionRequest) (err error) {
	trace := tracer.StartTrace(ctx, "AppsUsecase:SaveAppsPermission")
	defer trace.Finish()
	ctx = trace.Context()

	if err = uc.repoMongo.AppsRepo.Find(ctx, &shareddomain.Apps{ID: appID}); err != nil {
		return err
	}

	var fetchAllPermission func(domain.PermissionRequest) []shareddomain.Permission
	fetchAllPermission = func(data domain.PermissionRequest) (result []shareddomain.Permission) {
		data.ID = primitive.NewObjectID().Hex()
		currentPerm := shareddomain.Permission{Code: data.Code, AppsID: appID}
		if err := uc.repoMongo.PermissionRepo.Find(ctx, &currentPerm); err == nil {
			data.ID = currentPerm.ID
		}
		result = append(result, shareddomain.Permission{
			ID: data.ID, Code: data.Code, Icon: data.Icon, Name: data.Name, ParentID: data.ParentID,
			URL: data.URL, NewPage: data.NewPage,
		})
		for _, perm := range data.Childs {
			perm.ParentID = data.ID
			result = append(result, fetchAllPermission(perm)...)
		}
		return
	}

	for _, perm := range fetchAllPermission(permissions) {
		perm.AppsID = appID
		uc.repoMongo.PermissionRepo.Save(ctx, &perm)
	}

	return
}

func (uc *appsUsecaseImpl) GetAllPermissions(ctx context.Context, filter domain.FilterPermission) (data []domain.Permission, meta candishared.Meta, err error) {
	trace := tracer.StartTrace(ctx, "AppsUsecase:GetAllPermissions")
	defer trace.Finish()
	ctx = trace.Context()

	permissions, err := uc.repoMongo.PermissionRepo.FetchAll(ctx, filter)
	permParentGroups := make(map[string][]shareddomain.Permission)
	for _, perm := range permissions {
		permParentGroups[perm.ParentID] = append(permParentGroups[perm.ParentID], perm)
		if perm.ParentID == "" {
			// data.Permissions = append(data.Permissions, perm)
		}
		data = append(data, domain.Permission{FullCode: perm.Code})
	}

	return
}

func (uc *appsUsecaseImpl) GetAllUserPermissions(ctx context.Context, appsCode, userID string) (data []shareddomain.Permission, err error) {
	trace := tracer.StartTrace(ctx, "AppsUsecase:GetAllUserPermissions")
	defer trace.Finish()
	ctx = trace.Context()

	apps := shareddomain.Apps{Code: appsCode}
	if err := uc.repoMongo.AppsRepo.Find(ctx, &apps); err != nil {
		return data, errors.New("data not found")
	}

	filterACL := acldomain.ACLFilter{
		Filter: candishared.Filter{ShowAll: true},
		AppsID: apps.ID, UserID: userID,
	}
	acl, err := uc.repoMongo.ACLRepo.FetchAll(ctx, filterACL)
	if err != nil || len(acl) == 0 {
		return data, errors.New("ACL not found")
	}

	filterRole := acldomain.RoleListFilter{
		Filter: candishared.Filter{ShowAll: true},
		AppsID: apps.ID,
	}
	for _, access := range acl {
		filterRole.RoleIDs = append(filterRole.RoleIDs, access.RoleID)
	}

	roles, err := uc.repoMongo.RoleRepo.FetchAll(ctx, filterRole)
	if err != nil {
		return data, err
	}

	filterPermission := domain.FilterPermission{
		Filter: candishared.Filter{ShowAll: true}, AppID: apps.ID,
	}
	for _, role := range roles {
		for _, id := range role.Permissions {
			filterPermission.PermissionIDs = append(filterPermission.PermissionIDs, id)
		}
	}
	if len(filterPermission.PermissionIDs) == 0 {
		return data, errors.New("Permission not found")
	}

	permissions, err := uc.repoMongo.PermissionRepo.FetchAll(ctx, filterPermission)
	if err != nil {
		return data, err
	}

	permParentGroups := make(map[string][]shareddomain.Permission)
	for _, perm := range permissions {
		permParentGroups[perm.ParentID] = append(permParentGroups[perm.ParentID], perm)
		if perm.ParentID == "" {
			data = append(data, perm)
		}
	}

	var findAllChild func(parentID string) []shareddomain.Permission
	findAllChild = func(parentID string) (childs []shareddomain.Permission) {
		childs = permParentGroups[parentID]
		for i, c := range childs {
			c.AppsID = ""
			c.Childs = append(c.Childs, findAllChild(c.ID)...)
			childs[i] = c
		}
		return childs
	}

	for i, perm := range data {
		perm.AppsID = ""
		perm.Childs = append(perm.Childs, findAllChild(perm.ID)...)
		data[i] = perm
	}

	return
}

func (uc *appsUsecaseImpl) GetUserApps(ctx context.Context, userID string) (data []domain.UserApps, err error) {
	trace := tracer.StartTrace(ctx, "AppsUsecase:GetUserApps")
	defer trace.Finish()
	ctx = trace.Context()

	acl, err := uc.repoMongo.ACLRepo.FindByUserID(ctx, userID)
	if err != nil {
		return data, err
	}

	var appIDs, roleIDs []string
	roleAppMap := make(map[string]string)
	for _, a := range acl {
		appIDs = append(appIDs, a.AppsID)
		roleIDs = append(roleIDs, a.RoleID)
		roleAppMap[a.AppsID] = a.RoleID
	}

	roleMap := uc.repoMongo.RoleRepo.GroupByID(ctx, roleIDs...)

	apps, _ := uc.repoMongo.AppsRepo.FetchAll(ctx, domain.FilterApps{
		Filter: candishared.Filter{ShowAll: true}, IDs: appIDs,
	})

	for _, a := range apps {
		userApp := domain.UserApps{
			ID:          a.ID,
			Code:        a.Code,
			Name:        a.Name,
			Icon:        a.Icon,
			FrontendURL: a.FrontendURL,
			BackendURL:  a.BackendURL,
		}
		userApp.Role.ID = roleMap[roleAppMap[a.ID]].ID
		userApp.Role.Name = roleMap[roleAppMap[a.ID]].Name
		userApp.Role.Code = roleMap[roleAppMap[a.ID]].Code
		data = append(data, userApp)
	}

	return
}
