package ru.yandex.direct.api.v5.security;

import java.util.Objects;
import java.util.Set;

import javax.annotation.ParametersAreNonnullByDefault;

import com.google.common.collect.ImmutableSet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ru.yandex.direct.api.v5.ws.annotation.ServiceType;
import ru.yandex.direct.core.entity.user.model.ApiEnabled;
import ru.yandex.direct.core.entity.user.model.ApiUser;
import ru.yandex.direct.rbac.RbacRole;
import ru.yandex.direct.rbac.RbacService;
import ru.yandex.direct.rbac.UserPerminfo;

/**
 * Вспомогательный класс для дополнительных авторизационных проверок на api-сервисах,
 * в зависимости от типа сервиса: клиентский или агентский
 *
 * @author egorovmv
 */
@Component
@ParametersAreNonnullByDefault
public class DirectApiAuthorizationHelper {
    private static final Set<RbacRole> ROLES_FOR_AGENCY_AND_CLIENT_SERVICE =
            ImmutableSet.of(RbacRole.CLIENT, RbacRole.AGENCY);
    private final RbacService rbacService;

    @Autowired
    public DirectApiAuthorizationHelper(RbacService rbacService) {
        this.rbacService = Objects.requireNonNull(rbacService, "rbacService");
    }

    void authorize(
            DirectApiAuthentication authentication, ServiceType serviceType, String operationName) {
        // Если идем от оператора, который является внутренним пользователем, то проверять наличие доступа к API
        // не нужно. (См. DIRECT-72817)
        if (!authentication.getChiefOperator().getRole().isInternal() &&
                authentication.getChiefOperator().getApiEnabled() == ApiEnabled.NO) {
            throw SecurityErrors.newAccessToApiDeniedAccessDisabled();
        }

        switch (serviceType) {
            case CLIENT:
                checkClientServiceAccess(authentication, operationName);
                break;
            case AGENCY:
                checkAgencyServiceAccess(authentication);
                break;
            case AGENCY_AND_CLIENT:
                checkAgencyAndClientServiceAccess(authentication);
                break;
            case ALL:
                break;
            default:
                throw new IllegalStateException("Invalid api service type");
        }

        // Проверить есть ли права у chiefOperator на chiefSubclient
        if (!isOwner(authentication.getChiefOperator(), authentication.getChiefSubclient())) {
            throw SecurityErrors.newOperatorHasNotRightsOnTargetSubject();
        }
    }

    private void checkClientServiceAccess(
            DirectApiAuthentication authentication, String operationName) {
        if (authentication.getOperator().getRole() != RbacRole.CLIENT
                && authentication.isClientLoginEmpty()) {
            throw SecurityErrors.newClientLoginRequiredForOperator();
        }

        // целевой пользователь должен быть клиентом
        if (authentication.getSubjectUser().getRole() != RbacRole.CLIENT) {
            throw SecurityErrors.newTargetSubjectIsNotClient();
        }

        // Предполагаем, что проверка того что operator заблокирован происходит ранее,
        // поэтому здесь operator != subclient-у и достаточно просто проверить что subClient
        // не заблокирован. Также на всякий случай проверяем название операции без учета регистра
        // См. DIRECT-61921
        if (authentication.getSubjectUser().getStatusBlocked() && !"get".equalsIgnoreCase(operationName)) {
            throw SecurityErrors.newAccessToApiDeniedStatusBlocked();
        }
    }

    private void checkAgencyServiceAccess(DirectApiAuthentication authentication) {
        ApiUser operator = authentication.getOperator();

        // обычный клиент не может обращаться к агентскому сервису
        Long operatorUid = operator.getUid();
        UserPerminfo operatorPermInfo = rbacService.getUserPermInfo(operatorUid);
        boolean operatorCanHaveRelationships = operatorPermInfo.canHaveRelationship();
        RbacRole operatorRole = operator.getRole();
        if (operatorRole == RbacRole.CLIENT && !operatorCanHaveRelationships) {
            throw SecurityErrors.newClientCantAccessAgencyService();
        }

        // если оператор не агентство и не фрилансер, то должен быть указан Client-Login
        if ((operatorRole != RbacRole.AGENCY && !operatorCanHaveRelationships)
                && authentication.isClientLoginEmpty()) {
            throw SecurityErrors.newClientLoginRequiredForAgency();
        }

        // целевой пользователь должен быть агенством или фрилансером
        ApiUser subjectUser = authentication.getSubjectUser();
        if (subjectUser.getRole() == RbacRole.AGENCY) {
            //агенство
            return;
        }
        Long subjectUserUid = subjectUser.getUid();
        UserPerminfo userPermInfo = rbacService.getUserPermInfo(subjectUserUid);
        if (userPermInfo.canHaveRelationship()) {
            //фрилансер
            return;
        }
        throw SecurityErrors.newTargetSubjectIsNotAgencyOrFreelancer();
    }

    private void checkAgencyAndClientServiceAccess(DirectApiAuthentication authentication) {
        // если оператор не агентство или клиент, то должен быть указан Client-Login
        if (!ROLES_FOR_AGENCY_AND_CLIENT_SERVICE.contains(authentication.getChiefOperator().getRole())
                && authentication.isClientLoginEmpty()) {
            throw SecurityErrors.newClientLoginRequired();
        }

        // роль целевого пользователя должена соответствовать ожиданиям сервиса
        if (!ROLES_FOR_AGENCY_AND_CLIENT_SERVICE.contains(authentication.getSubjectUser().getRole())) {
            throw SecurityErrors.newTargetUserHasNotRequiredRole();
        }
    }

    private boolean isOwner(ApiUser chiefOperator, ApiUser chiefSubclient) {
        if (Objects.equals(chiefOperator.getUid(), chiefSubclient.getUid())) {
            return true;
        }
        return rbacService.isOwner(chiefOperator.getUid(), chiefSubclient.getUid());
    }
}
