package permissionsimp

import (
	"context"
	"encoding/json"
	"net/http"

	"github.com/tech/sendico/pkg/api/http/response"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mservice"
	"github.com/tech/sendico/pkg/mutil/mzap"
	"github.com/tech/sendico/server/interface/api/srequest"
	"github.com/tech/sendico/server/interface/api/sresponse"
	mutil "github.com/tech/sendico/server/internal/mutil/param"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
)

func (a *PermissionsAPI) changeRole(r *http.Request, account *model.Account, _ *sresponse.TokenData) http.HandlerFunc {
	orgRef, err := mutil.GetOrganizationRef(r)
	if err != nil {
		a.logger.Warn("Failed to restore organization reference", zap.Error(err), zap.String("organization_ref", mutil.GetOrganizationID(r)))
		return response.BadReference(a.logger, a.Name(), mutil.OrganizationRefName(), mutil.GetOrganizationID(r), err)
	}

	var req srequest.ChangeRole
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		a.logger.Warn("Failed to decode change role request", zap.Error(err))
		return response.BadPayload(a.logger, mservice.Roles, err)
	}

	ctx := r.Context()
	res, err := a.enforcer.Enforce(ctx, a.rolesPermissionRef, account.ID, orgRef, req.AccountRef, model.ActionUpdate)
	if err != nil {
		a.logger.Warn("Failed to check permissions while assigning new role", zap.Error(err),
			mzap.ObjRef("requesting_account_ref", account.ID), mzap.AccRef(req.AccountRef),
			mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef))
	}
	if !res {
		a.logger.Debug("Permission denied to set new role", mzap.ObjRef("requesting_account_ref", account.ID),
			mzap.AccRef(req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef))
		return response.AccessDenied(a.logger, a.Name(), "no permission to change user roles")
	}

	var roleDescription model.RoleDescription
	if err := a.rdb.Get(ctx, req.NewRoleDescriptionRef, &roleDescription); err != nil {
		a.logger.Warn("Failed to fetch and validate role description", zap.Error(err), mzap.ObjRef("requesting_account_ref", account.ID),
			mzap.AccRef(req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef))
		return response.Auto(a.logger, a.Name(), err)
	}

	return a.changeRoleImp(ctx, &req, orgRef, account)
}

func (a *PermissionsAPI) changeRoleImp(ctx context.Context, req *srequest.ChangeRole, organizationRef bson.ObjectID, account *model.Account) http.HandlerFunc {
	roles, err := a.enforcer.GetRoles(ctx, req.AccountRef, organizationRef)
	// TODO: add check that role revocation won't leave venue without the owner
	if err != nil {
		a.logger.Warn("Failed to fetch account roles", zap.Error(err), mzap.ObjRef("requesting_account_ref", account.ID),
			mzap.AccRef(req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef))
		return response.Auto(a.logger, a.Name(), err)
	}
	for _, role := range roles {
		if err := a.manager.Role().Revoke(ctx, role.DescriptionRef, req.AccountRef, organizationRef); err != nil {
			a.logger.Warn("Failed to revoke old role", zap.Error(err), mzap.ObjRef("requesting_account_ref", account.ID),
				mzap.AccRef(req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef),
				mzap.ObjRef("role_ref", role.DescriptionRef))
			// continue...
		}
	}

	role := model.Role{
		AccountRef:      req.AccountRef,
		OrganizationRef: organizationRef,
		DescriptionRef:  req.NewRoleDescriptionRef,
	}
	if err := a.manager.Role().Assign(ctx, &role); err != nil {
		a.logger.Warn("Failed to assign new role", zap.Error(err), mzap.ObjRef("requesting_account_ref", account.ID),
			mzap.AccRef(req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef),
			mzap.ObjRef("role_ref", req.NewRoleDescriptionRef))
		return response.Auto(a.logger, a.Name(), err)
	}

	return response.Success(a.logger)
}
