package ru.yandex.webmaster3.internal.metrika.counter;

import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import ru.yandex.webmaster3.core.http.WriteAction;
import ru.yandex.webmaster3.core.metrika.counters.AnonymousCounterBinding;
import ru.yandex.webmaster3.core.metrika.counters.CounterBindingStateEnum;
import ru.yandex.webmaster3.core.metrika.counters.CounterRequestTypeEnum;
import ru.yandex.webmaster3.core.metrika.counters.MetrikaCountersUtil;
import ru.yandex.webmaster3.core.util.TimeUtils;
import ru.yandex.webmaster3.core.worker.client.WorkerClient;
import ru.yandex.webmaster3.core.worker.task.MetrikaCounterBindingStateChangeTaskData;
import ru.yandex.webmaster3.core.worker.task.MetrikaCounterBindingTaskData;
import ru.yandex.webmaster3.internal.common.InternalAction;
import ru.yandex.webmaster3.internal.common.security.ActionInternalGrant;
import ru.yandex.webmaster3.internal.common.security.InternalGrant;
import ru.yandex.webmaster3.storage.metrika.MetrikaCounterBindingService;
import ru.yandex.webmaster3.storage.user.service.UserHostsService;
import ru.yandex.wmtools.common.util.uri.UriUtils;

/**
 * Created by ifilippov5 on 24.10.17.
 */
@WriteAction
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@ActionInternalGrant(InternalGrant.METRIKA)
public class MetrikaCounterBindingAction extends InternalAction<MetrikaBindingRequest, MetrikaBindingResponse> {
    private static final Logger log = LoggerFactory.getLogger(MetrikaCounterBindingAction.class);

    private final MetrikaCounterBindingService metrikaCounterBindingService;
    private final UserHostsService userHostsService;
    private final WorkerClient workerClient;

    @Setter
    private CounterRequestTypeEnum action;

    private static Map<CounterRequestTypeEnum, EnumSet<CounterBindingStateEnum>> mapActionExpectedState = new HashMap<>();

    static {
        mapActionExpectedState.put(CounterRequestTypeEnum.REJECT,
                EnumSet.of(CounterBindingStateEnum.WEBMASTER_REQUEST));

        mapActionExpectedState.put(CounterRequestTypeEnum.CONFIRM,
                EnumSet.of(CounterBindingStateEnum.METRIKA_REJECT, CounterBindingStateEnum.WEBMASTER_REQUEST,
                        CounterBindingStateEnum.APPROVED)); // в теории, мы можем заапрувить раньше метрики

        mapActionExpectedState.put(CounterRequestTypeEnum.CANCEL,
                EnumSet.of(CounterBindingStateEnum.METRIKA_REQUEST));
    }

    @Override
    public MetrikaBindingResponse process(MetrikaBindingRequest request) {
        String canonicalDomain = MetrikaCountersUtil.domainToCanonicalAscii(request.getDomain());
        long counterId = request.getCounterId();
        long userId = request.getUserId();

        log.info("Action {}:{} for {}:{}", action, action.getPath(), canonicalDomain, counterId);
        try {
            UriUtils.verifyASCIIDomain(canonicalDomain);
        } catch (IllegalArgumentException e) {
            log.error("{} is invalid domain", request.getDomain(), e);
            return new MetrikaBindingResponse.InvalidDomainError();
        }

        CounterBindingStateEnum state;
        state = metrikaCounterBindingService.getState(canonicalDomain, counterId);

        log.info("Counter {} current state: {}", counterId, state);
        EnumSet<CounterBindingStateEnum> allowedStates = mapActionExpectedState.get(action);
        if (allowedStates != null && !allowedStates.contains(state)) {
            log.error("Inconsistent counter {}:{} state {} for action {}", canonicalDomain, counterId, state, action);
            return new MetrikaBindingResponse.InconsistentCounterStateError(state.getMeaning());
        }

        boolean isAutoApprove = false;
        if (action == CounterRequestTypeEnum.CREATE) {
            // если у пользователя метрики подтвержден сайт, то сразу переводим привязку в состояние approved
            if (userHostsService.isAnyHostVerified(userId,
                    MetrikaCountersUtil.generateHostIds(canonicalDomain).collect(Collectors.toList()))) {
                log.info("Host is verified for user {}, approving counter {}", userId, counterId);
                isAutoApprove = true;
            }
        }

        CounterBindingStateEnum newState = isAutoApprove ?
                CounterBindingStateEnum.APPROVED :
                MetrikaCountersUtil.nextState(state, MetrikaCountersUtil.getActionFromMetrikaType(action));

        log.info("Counter {}:{} next state: {}", canonicalDomain, counterId, newState);
        if (state != newState) {
            String origin = action == CounterRequestTypeEnum.CREATE ? AnonymousCounterBinding.ORIGIN_METRIKA : null;
            String userLogin = metrikaCounterBindingService.getUserLogin(userId);
            DateTime now = DateTime.now(TimeUtils.EUROPE_MOSCOW_ZONE);

            if (isAutoApprove) {
                metrikaCounterBindingService.updateStateWithMetrikaAndWebmasterUser(canonicalDomain, newState, counterId,
                        userLogin, now, userId, origin);
            } else {
                metrikaCounterBindingService.updateStateWithMetrikaUser(canonicalDomain, newState, counterId,
                        userLogin, now, userId, null);

                workerClient.checkedEnqueueTask(new MetrikaCounterBindingTaskData(action, canonicalDomain, userId, userLogin, counterId));
            }

            workerClient.checkedEnqueueTask(new MetrikaCounterBindingStateChangeTaskData(
                    action,
                    canonicalDomain,
                    userId,
                    userLogin,
                    counterId,
                    state,
                    newState,
                    DateTime.now()
            ));
        }

        return new MetrikaBindingResponse.NormalResponse(newState.getMeaning());
    }

}
