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

import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ru.yandex.direct.api.v5.context.units.UnitsContext;
import ru.yandex.direct.api.v5.security.DirectApiAuthentication;
import ru.yandex.direct.api.v5.security.DirectApiPreAuthentication;
import ru.yandex.direct.api.v5.units.exception.NoPreAuthDataException;
import ru.yandex.direct.api.v5.units.logging.UnitsLogDataFactory;
import ru.yandex.direct.core.entity.user.model.ApiUser;
import ru.yandex.direct.core.units.api.UnitsBalance;

@Component
@ParametersAreNonnullByDefault
public class UnitsContextFactory {

    private static final Logger logger = LoggerFactory.getLogger(UnitsContextFactory.class);

    private final ApiUnitsService apiUnitsService;
    private final UnitsHolderDetector unitsHolderDetector;
    private final UnitsLogDataFactory unitsLogDataFactory;

    @Autowired
    public UnitsContextFactory(
            ApiUnitsService apiUnitsService,
            UnitsHolderDetector unitsHolderDetector,
            UnitsLogDataFactory unitsLogDataFactory) {
        this.apiUnitsService = apiUnitsService;
        this.unitsHolderDetector = unitsHolderDetector;
        this.unitsLogDataFactory = unitsLogDataFactory;
    }

    /**
     * Собрать информацию о держателях баллов и их балансах.
     * Нас интересуют два баланса: первый – клиента, для списания баллов за вызов сервиса,а также за число объектов;
     * и второй – оператора, для списания баллов в случае ошибки уровня запроса.
     *
     * @param preAuth       объект предварительной аутентификации.
     * @param operationCost стоимость операции, запрошенной пользователем.
     * @return {@link UnitsContext}, содержащий информацию о балансах держателей баллов для списания
     * за выполнение запроса, или содержащий {@link RuntimeException} если эту информацию собрать не удалось,
     * в том числе в случае, если {@link DirectApiAuthentication} {@code == null}.
     */
    UnitsContext createUnitsContext(@Nullable DirectApiPreAuthentication preAuth, int operationCost) {
        try {
            if (preAuth == null) {
                throw new NoPreAuthDataException();
            }
            return buildUnitsContext(preAuth, operationCost);
        } catch (RuntimeException e) {
            if (NoPreAuthDataException.class.isAssignableFrom(e.getClass())) {
                // Нет особого смысла писать это в error
                logger.info("No pre-auth data");
            } else {
                logger.warn("Exception caught while creating units info.", e);
            }
            return UnitsContext.createOnFail(e);
        }
    }

    UnitsContext createUnitsContext(@Nullable DirectApiPreAuthentication preAuth) {
        return createUnitsContext(preAuth, 0);
    }

    private UnitsContext buildUnitsContext(DirectApiPreAuthentication preAuth, int operationCost) {
        ApiUser operator = unitsHolderDetector.detectOperatorUnitsHolder(preAuth);
        ApiUser unitsHolder = unitsHolderDetector.detectUnitsHolder(preAuth, operationCost);
        ApiUser unitsUsedUser = unitsHolderDetector.getUnitsHolderToDisplay(preAuth, operationCost);

        logger.debug("Units holder: {} {}", unitsHolder.getLogin(), unitsHolder.getRole());
        logger.debug("Request operator: {} {}", operator.getLogin(), operator.getRole());
        logger.debug("Units used login: {}", unitsUsedUser.getLogin());

        return UnitsContext.create(
                getUnitsBalance(preAuth, unitsHolder),
                getUnitsBalance(preAuth, operator),
                unitsUsedUser,
                operator,
                unitsLogDataFactory.createUnitsLogData(preAuth));
    }

    private UnitsBalance getUnitsBalance(DirectApiPreAuthentication preAuth, ApiUser user) {
        return apiUnitsService.getAdjustedUnitsBalance(preAuth, user);
    }
}
