package impl

import (
	"context"
	"time"

	"github.com/go-sonic/sonic/cache"
	"github.com/go-sonic/sonic/model/entity"
	"github.com/go-sonic/sonic/service"
	"github.com/go-sonic/sonic/util/xerr"
)

type authenticateServiceImpl struct {
	CategoryService service.CategoryService
	Cache           cache.Cache
}

func NewAuthenticateService(categoryService service.CategoryService, cache cache.Cache) service.AuthenticateService {
	return &authenticateServiceImpl{
		CategoryService: categoryService,
		Cache:           cache,
	}
}

func (a *authenticateServiceImpl) PostAuthenticate(ctx context.Context, post *entity.Post, password string) (bool, error) {
	panic("implement me")
}

func (a *authenticateServiceImpl) CategoryAuthenticate(ctx context.Context, categoryID int32, password string) (bool, error) {
	categories, err := a.CategoryService.ListAll(ctx, nil)
	if err != nil {
		return false, nil
	}
	categoryIDMap := make(map[int32]*entity.Category)
	for _, category := range categories {
		categoryIDMap[category.ID] = category
	}
	return a.doCategoryAuthenticate(ctx, categoryIDMap, categoryID, password)
}

func (a *authenticateServiceImpl) doCategoryAuthenticate(ctx context.Context, categoryIDMap map[int32]*entity.Category, categoryID int32, password string) (bool, error) {
	category, ok := categoryIDMap[categoryID]
	if !ok || category == nil {
		return false, nil
	}
	permissionsMap, err := a.getAccessPermission(ctx)
	if err != nil {
		return false, err
	}
	if category.Password != "" {
		if _, ok := permissionsMap[cache.BuildCategoryPermissionKey(categoryID)]; ok {
			return true, nil
		}
		if category.Password == password {
			err := a.setAccessPermission(ctx, cache.BuildCategoryPermissionKey(categoryID))
			return false, xerr.WithErrMsgf(err, "set category permission cache failed")
		}
		return false, nil
	}
	if category.ParentID == 0 {
		return true, nil
	}
	return a.doCategoryAuthenticate(ctx, categoryIDMap, category.ParentID, password)
}

func (a *authenticateServiceImpl) getAccessPermission(ctx context.Context) (map[string]struct{}, error) {
	accessKey, err := cache.BuildAccessPermissionKey(ctx)
	if err != nil {
		return nil, err
	}
	permissions, ok := a.Cache.Get(accessKey)
	if !ok {
		return make(map[string]struct{}), nil
	}
	permissionsMap, ok := permissions.(map[string]struct{})
	if !ok {
		return nil, xerr.NoType.New("cache value is not map[string]struct{}")
	}
	return permissionsMap, nil
}

func (a *authenticateServiceImpl) setAccessPermission(ctx context.Context, permissionKey string) error {
	accessKey, err := cache.BuildAccessPermissionKey(ctx)
	if err != nil {
		return err
	}
	permissions, ok := a.Cache.Get(accessKey)
	if !ok {
		return nil
	}
	permissionsMap, ok := permissions.(map[string]struct{})
	if !ok {
		return xerr.NoType.New("cache value is not map[string]struct{}")
	}
	permissionsMap[permissionKey] = struct{}{}
	a.Cache.Set(accessKey, permissionsMap, time.Hour*24)
	return nil
}