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

import java.util.Optional;

import javax.annotation.ParametersAreNonnullByDefault;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ru.yandex.direct.api.v5.security.DirectApiPreAuthentication;
import ru.yandex.direct.api.v5.units.exception.NotAnAgencyException;
import ru.yandex.direct.core.entity.user.model.ApiUser;
import ru.yandex.direct.core.entity.user.service.ApiUserService;
import ru.yandex.direct.core.units.service.UnitsService;
import ru.yandex.direct.rbac.RbacRole;

@Component
@ParametersAreNonnullByDefault
public class UnitsHolderDetector {

    private final ApiUserService apiUserService;
    private final UnitsService unitsService;

    @Autowired
    public UnitsHolderDetector(ApiUserService apiUserService, UnitsService unitsService) {
        this.apiUserService = apiUserService;
        this.unitsService = unitsService;
    }

    /**
     * Пользователь (в общем случае - некий представитель клиента или агентства), логин которого
     * будет указан в заголовке ответа {@code Units-Used-Login}.
     *
     * @param preAuth       объект аутентификации.
     * @param operationCost стоимость операции, запрошенной пользователем.
     * @return {@link ApiUser}, оператор (владелец токена) или пользователь,
     * чей логин указан в заголовке запроса {@code Client-Login},
     * в зависимости от флага {@code Use-Operator-Units}.
     */
    public ApiUser getUnitsHolderToDisplay(DirectApiPreAuthentication preAuth, int operationCost) {
        switch (preAuth.getDirectApiCredentials().getUseOperatorUnitsMode()) {
            case TRUE:
                return preAuth.getOperator();
            case AUTO:
                Optional<ApiUser> subclientOrBrandChief = getSubclientOrBrandChief(preAuth);
                if (subclientOrBrandChief.isPresent()
                        && unitsService.getUnitsBalance(subclientOrBrandChief.get()).isAvailable(operationCost)) {
                    return preAuth.getSubjectUser().orElse(preAuth.getOperator());
                } else {
                    return preAuth.getOperator();
                }
            case FALSE:
                return preAuth.getSubjectUser().orElse(preAuth.getOperator());
            default:
                throw new IllegalStateException("Unknown use operator units mode.");
        }
    }

    /**
     * Определить пользователя, с которого будут списаны баллы
     * за вызов сервиса.
     *
     * @param preAuth       объект аутентификации.
     * @param operationCost стоимость операции, запрошенной пользователем.
     * @return {@link ApiUser}, агентство, субклиент или главный клиент бренда,
     * в зависимости от значения заголовка {@code Use-Operator-Units} и
     * соотношения лимитов клиента и его бренда.
     */
    public ApiUser detectUnitsHolder(DirectApiPreAuthentication preAuth, int operationCost) {
        switch (preAuth.getDirectApiCredentials().getUseOperatorUnitsMode()) {
            case TRUE:
                return getChiefOperatorIfItIsAnAgency(preAuth);
            case AUTO:
                Optional<ApiUser> subclientOrBrandChief = getSubclientOrBrandChief(preAuth);
                if (subclientOrBrandChief.isPresent()
                        && unitsService.getUnitsBalance(subclientOrBrandChief.get()).isAvailable(operationCost)) {
                    return subclientOrBrandChief.get();
                } else {
                    return getChiefOperatorIfItIsAnAgency(preAuth);
                }
            case FALSE:
                return getSubclientOrBrandChief(preAuth).orElse(preAuth.getChiefOperator());
            default:
                throw new IllegalStateException("Unknown use operator units mode.");
        }
    }

    /**
     * Определить пользователя, с которого будут списаны баллы в случае
     * ошибки валидации запроса.
     *
     * @param preAuth объект аутентификации.
     * @return {@link ApiUser}, агентство, если его представитель является оператором;
     * если оператор - субклиент, будет возвращён его шеф-представитель или главный
     * клиент бренда, в зависимости от лимитов.
     */
    public ApiUser detectOperatorUnitsHolder(DirectApiPreAuthentication preAuth) {
        ApiUser chiefOperator = preAuth.getChiefOperator();
        if (isAgency(chiefOperator)) {
            return chiefOperator;
        } else {
            return getSubclientOrBrandChief(chiefOperator);
        }
    }

    /**
     * Получить оператора, за чьи баллы будет произведена операция.
     * Оператор должен иметь роль агентства.
     *
     * @param preAuth объект аутентификации.
     * @return Агенство, с которого будут списаны баллы за операцию; {@link ApiUser}.
     * @throws NotAnAgencyException если оператор не является агентством.
     */
    private ApiUser getChiefOperatorIfItIsAnAgency(DirectApiPreAuthentication preAuth) {
        ApiUser chiefOperator = preAuth.getChiefOperator();
        if (!isAgency(chiefOperator)) {
            throw new NotAnAgencyException();
        }
        return chiefOperator;
    }

    /**
     * Получить субклиента или главного клиента бренда для списания баллов.
     *
     * @param preAuth объект аутентификации
     * @return {@link ApiUser}, Субклиент или главный клиент бренда (если имеется)
     * - в зависимости от того, у кого выше лимит. Если лимиты равны, возвращается главный клиент бренда.
     */
    private Optional<ApiUser> getSubclientOrBrandChief(DirectApiPreAuthentication preAuth) {
        return preAuth.getChiefSubjectUser().map(this::getSubclientOrBrandChief);
    }

    private ApiUser getSubclientOrBrandChief(ApiUser subclient) {
        ApiUser brandChief = apiUserService.getBrandChiefRepFor(subclient);
        if (brandChief == null || getLimit(subclient) > getLimit(brandChief)) {
            return subclient;
        } else {
            return brandChief;
        }
    }

    private boolean isAgency(ApiUser user) {
        return user.getRole() == RbacRole.AGENCY;
    }

    private int getLimit(ApiUser user) {
        return unitsService.getLimit(user);
    }
}
