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

import java.util.Optional;

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

import ru.yandex.direct.api.v5.context.ApiContext;
import ru.yandex.direct.core.entity.user.model.ApiUser;
import ru.yandex.direct.core.units.api.UnitsBalance;

import static com.google.common.base.Preconditions.checkState;

@ParametersAreNonnullByDefault
public class UnitsContext {

    /**
     * Баланс клиента, являющегося unitsHolder'ом текущего запроса
     */
    private final UnitsBalance unitsBalance;

    /**
     * Ошибка определения unitsHolder'а или его баланса
     */
    private final RuntimeException unitsBalanceException;

    /**
     * Баланс оператора запроса или его бренда. Хранится для списания баллов в случае ошибок валидации запроса.
     */
    private final UnitsBalance operatorUnitsBalance;

    /**
     * Пользователь, чей логин будет отображён в заголовке ответа {@code Units-Used-Login},
     * указывающем, с кого списались баллы.
     */
    private ApiUser unitsUsedUser;

    /**
     * В случае ошибки запроса в заголовке ответа {@code Units-Used-Login} необходимо указать оператора, а не
     * пользователя, с которого мы планировали снять баллы.
     */
    private ApiUser operator;

    /**
     * Структура для записи в лог информации об участниках запроса и списанных баллах.
     */
    private UnitsLogData unitsLogData;

    private UnitsContext(@Nullable UnitsBalance unitsBalance,
                         @Nullable UnitsBalance operatorUnitsBalance,
                         @Nullable ApiUser unitsUsedUser,
                         @Nullable ApiUser operator,
                         @Nullable UnitsLogData unitsLogData,
                         @Nullable RuntimeException unitsBalanceException) {
        this.unitsBalance = unitsBalance;
        this.unitsBalanceException = unitsBalanceException;
        this.operatorUnitsBalance = operatorUnitsBalance;
        this.unitsUsedUser = unitsUsedUser;
        this.operator = operator;
        this.unitsLogData = unitsLogData;
    }

    public static UnitsContext create(UnitsBalance unitsBalance,
                                      UnitsBalance operatorUnitsBalance,
                                      ApiUser unitsUsedUser,
                                      ApiUser operator,
                                      UnitsLogData unitsLogData) {
        return new UnitsContext(unitsBalance,
                operatorUnitsBalance,
                unitsUsedUser,
                operator,
                unitsLogData,
                null);
    }

    public static UnitsContext createOnFail(RuntimeException unitsBalanceException) {
        return new UnitsContext(
                null,
                null,
                null,
                null,
                null,
                unitsBalanceException
        );
    }

    /**
     * @return Баланс баллов клиента ({@link UnitsBalance} {@link #unitsBalance}), если он был ранее определён.
     * @throws RuntimeException {@link #unitsBalanceException} в случае, если баланс не был определён
     *                          на момент создания объекта.
     */
    public UnitsBalance getUnitsBalanceOrFail() {
        if (unitsBalance != null) {
            return unitsBalance;
        }
        checkState(unitsBalanceException != null, "either unitsBalance or unitsBalanceException "
                + "must be defined");
        throw unitsBalanceException;
    }

    @Nullable
    public UnitsBalance getUnitsBalance() {
        return unitsBalance;
    }

    public Optional<RuntimeException> getUnitsBalanceException() {
        return Optional.ofNullable(unitsBalanceException);
    }

    /**
     * @return ранее сохранённое в {@link ApiContext} значение {@link UnitsBalance} оператора
     */
    @Nullable
    public UnitsBalance getOperatorUnitsBalance() {
        return operatorUnitsBalance;
    }

    @Nullable
    public ApiUser getUnitsUsedUser() {
        return unitsUsedUser;
    }

    @Nullable
    public ApiUser getOperator() {
        return operator;
    }

    /**
     * @return Структура для записи в лог данных о событии списания баллов.
     */
    public UnitsLogData getUnitsLogData() {
        return unitsLogData;
    }

    @Override
    public String toString() {
        return "UnitsContext{" +
                "unitsBalance=" + unitsBalance +
                ", unitsBalanceException=" + unitsBalanceException +
                ", operatorUnitsBalance=" + operatorUnitsBalance +
                ", unitsUsedUser=" + unitsUsedUser +
                ", unitsLogData=" + unitsLogData +
                '}';
    }
}
