package ru.yandex.webmaster3.admin.security;

import com.google.common.primitives.Longs;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Required;
import ru.yandex.autodoc.common.doc.params.ParamDescriptor;
import ru.yandex.autodoc.common.doc.types.ValueType;
import ru.yandex.webmaster3.admin.security.action.AdminAction;
import ru.yandex.webmaster3.admin.security.action.AdminUserIdAware;
import ru.yandex.webmaster3.admin.security.service.AdminUsersService;
import ru.yandex.webmaster3.core.http.Action;
import ru.yandex.webmaster3.core.http.ActionEffect;
import ru.yandex.webmaster3.core.http.ActionRequest;
import ru.yandex.webmaster3.core.http.InternalAction;
import ru.yandex.webmaster3.core.http.RequestFilter;
import ru.yandex.webmaster3.core.http.internal.ActionReflectionUtils;
import ru.yandex.webmaster3.core.http.request.RequestContext;
import ru.yandex.webmaster3.storage.admin.security.AccessActionEnum;
import ru.yandex.webmaster3.storage.admin.security.AccessObjectEnum;
import ru.yandex.webmaster3.storage.admin.security.Permission;
import ru.yandex.webmaster3.storage.admin.security.Role;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

/**
 * @author avhaliullin
 */
public class AdminRequestFilter implements RequestFilter<ActionRequest, AdminRequestFilterResponse> {
    private static final String ADMIN_USER_ID_PARAM = "adminUserId";

    private static final Permission VIRTUAL_LOGIN_PERMISSION = new Permission(AccessActionEnum.READ, AccessObjectEnum.VIRUAL_LOGIN);

    private static final ParamDescriptor ADMIN_USER_ID_PARAM_DESC = new ParamDescriptor(
            ADMIN_USER_ID_PARAM,
            true,
            ValueType.INT64,
            null,
            "uid сотрудника",
            Collections.emptyList()
    );

    private AdminUsersService adminUsersService;

    @Override
    public boolean isApplicable(Action action) {
        if (action.getClass().isAnnotationPresent(InternalAction.class)) {
            return true;
        }
        if (ActionReflectionUtils.getActionEffect(action.getClass()) == null) {
            throw new RuntimeException("Action " + action.getClass() + " isn't annotated with ReadAction, WriteAction or InternalAction");
        }
        return true;
    }

    @Override
    public AdminRequestFilterResponse beforeRequest(RequestContext ctx, ActionRequest req) {
        Action action = ctx.getAction();
        if (action.getClass().isAnnotationPresent(InternalAction.class)) {
            return null;
        }
        String userIdString = ctx.getHttpRequest().getParameter(ADMIN_USER_ID_PARAM);
        if (StringUtils.isEmpty(userIdString)) {
            return new AdminRequestFilterResponse.NotLoggedIn(getClass());
        }
        Long userId = Longs.tryParse(userIdString);
        if (userId == null) {
            return new AdminRequestFilterResponse.NotLoggedIn(getClass());
        }
        Set<Role> roles = adminUsersService.listUserRoles(userId);
        if (roles.isEmpty()) {
            return new AdminRequestFilterResponse.NotAuthorized(getClass());
        }

        if (action instanceof AdminAction) {
            AdminAction adminAction = (AdminAction) action;
            boolean hasRequiredPermission = adminAction.getAccessObject() == null;
            if (!hasRequiredPermission) {
                Permission requiredPermission = new Permission(
                        getAccessAction(adminAction),
                        adminAction.getAccessObject());
                for (Role role : roles) {
                    if (role.getPermissions().contains(requiredPermission)) {
                        hasRequiredPermission = true;
                        break;
                    }
                }
            }
            if (!hasRequiredPermission) {
                return new AdminRequestFilterResponse.NotAuthorized(getClass());
            }

            ((AdminUserIdAware) req).setAdminUserId(userId);
        } else {
            for (Role role : roles) {
                if (role.getPermissions().contains(VIRTUAL_LOGIN_PERMISSION)) {
                    return null;
                }
            }
            return new AdminRequestFilterResponse.NotAuthorized(getClass());
        }
        return null;
    }

    private AccessActionEnum getAccessAction(AdminAction<?, ?> action) {
        ActionEffect actionEffect = ActionReflectionUtils.getActionEffect(action.getClass());
        switch (actionEffect) {
            case READ:
                return AccessActionEnum.READ;
            case WRITE:
                return AccessActionEnum.WRITE;
            default:
                throw new RuntimeException("Unknown action effect " + actionEffect);
        }
    }

    @Override
    public List<ParamDescriptor> clarifyParameters(List<ParamDescriptor> params) {
        List<ParamDescriptor> result = new ArrayList<>(params);
        result.add(ADMIN_USER_ID_PARAM_DESC);
        return result;
    }

    @Required
    public void setAdminUsersService(AdminUsersService adminUsersService) {
        this.adminUsersService = adminUsersService;
    }
}
