package permissionsimp

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

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

func (a *PermissionsAPI) changePolicies(r *http.Request, account *model.Account, _ *sresponse.TokenData) http.HandlerFunc {
	var req srequest.ChangePolicies
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		a.logger.Warn("Failed to decode role policies change request", zap.Error(err))
		return response.BadPayload(a.logger, mservice.Roles, err)
	}
	if req.Add != nil && req.Remove != nil {
		for _, addItem := range *req.Add {
			for _, removeItem := range *req.Remove {
				if addItem == removeItem {
					a.logger.Debug("Duplicate policies found, rejecting policies update request", zap.Any("add", &addItem), zap.Any("remove", &removeItem))
					return response.BadRequest(a.logger, a.Name(), "invalid_policies_change_request", "duplicate policies found in 'add' and 'remove' fields")
				}
			}
		}
	}

	if _, err := a.tf.CreateTransaction().Execute(r.Context(), func(ctx context.Context) (any, error) {
		return a.changePoliciesImp(ctx, account, &req)
	}); err != nil {
		a.logger.Debug("Rolling policies changes back", zap.Error(err))
		return response.Auto(a.logger, a.Name(), err)
	}

	return response.Success(a.logger)
}

func (a *PermissionsAPI) changePoliciesImp(
	ctx context.Context,
	account *model.Account,
	req *srequest.ChangePolicies,
) (any, error) {
	// helper that runs through each change-item, enforces the right action,
	// and then calls apply(item) if enforcement passes.
	handle := func(items *[]model.RolePolicy, action model.Action, opName string, apply func(context.Context, *model.RolePolicy) error) error {
		for _, it := range *items {
			// 1) permission check
			ok, err := a.enforcer.Enforce(ctx, a.policiesPermissionRef, account.ID, it.OrganizationRef, bson.NilObjectID, action)
			if err != nil {
				a.logger.Warn(fmt.Sprintf("failed to enforce permission while %s policy", opName), zap.Error(err), zap.Any(opName, &it))
				return err
			}
			if !ok {
				a.logger.Debug(fmt.Sprintf("policy %s denied", opName))
				return merrors.AccessDenied(mservice.Policies, string(action), bson.NilObjectID)
			}

			// 2) perform the add/remove
			if err := apply(ctx, &it); err != nil {
				a.logger.Warn(fmt.Sprintf("failed to %s role policy", opName), zap.Error(err), zap.Any("policy", &it))
				return err
			}
		}
		return nil
	}

	// REMOVE
	if req.Remove != nil {
		if err := handle(req.Remove, model.ActionDelete, "remove", func(ctx context.Context, it *model.RolePolicy) error {
			return a.auth.Permission().RevokeFromRole(ctx, it)
		}); err != nil {
			return nil, err
		}
	}

	// ADD
	if req.Add != nil {
		if err := handle(req.Add, model.ActionCreate, "add", func(ctx context.Context, it *model.RolePolicy) error {
			return a.auth.Permission().GrantToRole(ctx, it)
		}); err != nil {
			return nil, err
		}
	}

	return struct{}{}, nil
}
