package ru.yandex.webmaster3.viewer.http.turbo.statistics;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import com.google.common.collect.Sets;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ru.yandex.autodoc.common.doc.annotation.Description;
import ru.yandex.webmaster3.core.blackbox.service.BlackboxUsersService;
import ru.yandex.webmaster3.core.http.ActionResponse;
import ru.yandex.webmaster3.core.http.ReadAction;
import ru.yandex.webmaster3.core.metrics.Category;
import ru.yandex.webmaster3.core.turbo.model.TurboHostSettings;
import ru.yandex.webmaster3.core.turbo.model.commerce.TurboCommerceSettings;
import ru.yandex.webmaster3.core.util.WwwUtil;
import ru.yandex.webmaster3.storage.settings.history.SettingsHistoryService;
import ru.yandex.webmaster3.storage.util.JsonDBMapping;
import ru.yandex.webmaster3.viewer.http.AbstractUserVerifiedHostAction;
import ru.yandex.webmaster3.viewer.http.AbstractUserVerifiedHostRequest;

import static ru.yandex.webmaster3.storage.settings.history.SettingsHistoryType.TURBO_DOMAIN;

@ReadAction
@Category("turbo")
@Description("История изменения оплаты")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Component("/turbo/settings/ecommerce/payments/history")
public class TurboSettingsEcommercePaymentHistory extends AbstractUserVerifiedHostAction<TurboSettingsEcommercePaymentHistory.Request, TurboSettingsEcommercePaymentHistory.NormalResponse> {

    private final SettingsHistoryService settingsHistoryService;
    private final BlackboxUsersService blackboxExternalYandexUsersService;

    private static final PaymentSettingsType[] PAYMENTS_TYPES = PaymentSettingsType.values();

    @Override
    public NormalResponse process(Request request) {
        String domain = WwwUtil.cutWWWAndM(request.getHostId());
        final List<SettingsHistoryService.LogSettingInfo> settingInfos = settingsHistoryService.turboChangesForHostAndType(domain, TURBO_DOMAIN);
        settingInfos.sort(Comparator.comparing(SettingsHistoryService.LogSettingInfo::getUpdateDate));
        Optional<TurboCommerceSettings> lastCommerceSettingsOptional = Optional.empty();

        final Set<Long> userIds = new HashSet<>();
        final Map<PaymentSettingsType, List<PaymentState>> paymentInfo = new HashMap<>();

        for (var it : settingInfos) {
            final TurboHostSettings turboHostSettings = JsonDBMapping.readValue(it.getNewState(), TurboHostSettings.class);
            final Optional<TurboCommerceSettings> turboCommerceSettingsOptional = Optional.of(turboHostSettings).map(TurboHostSettings::getCommerceSettings);

            for (var type : PAYMENTS_TYPES) {
                var oldSettings = getPropertyByType(lastCommerceSettingsOptional, type);
                var newSettings = getPropertyByType(turboCommerceSettingsOptional, type);
                final Map<String, Object> changes = PaymentChange.computeChanges(oldSettings, newSettings, type);
                if (changes.isEmpty()) {
                    continue;
                }
                lastCommerceSettingsOptional = turboCommerceSettingsOptional;
                if (it.getUserId() == -1 || it.getUserId() == 0) {
                    continue;
                }
                paymentInfo.computeIfAbsent(type, x -> new ArrayList<>()).add(new PaymentState(it.getUserId(), it.getUpdateDate(), changes));
                userIds.add(it.getUserId());
            }
        }
        final Map<Long, String> loginByUserId;
        if (userIds.isEmpty()) {
            loginByUserId = Collections.emptyMap();
        } else {
            loginByUserId = blackboxExternalYandexUsersService.mapUserIdsToLogins(userIds);
        }

        // проставляем логины
        for (var it : paymentInfo.values()) {
            for (var state : it) {
                state.setLogin(loginByUserId.get(state.userId));
            }
            it.sort(Comparator.comparing(PaymentState::getUpdateDate).reversed());
        }

        return new NormalResponse(paymentInfo);
    }

    Map<String, Object> getPropertyByType(Optional<TurboCommerceSettings> commerceSettings, PaymentSettingsType type) {
        Map<String, Object> result = new HashMap<>();
        switch (type) {
            case PAYMENT_INFO:
                commerceSettings.map(TurboCommerceSettings::getPaymentsSettings)
                        .ifPresent(x -> {
                                    result.put("token", x.getToken());
                                    result.put("merchantInfo", x.getMerchantInfo());
                                }
                        );
                break;
            case ENABLED:
                commerceSettings.map(TurboCommerceSettings::getPaymentsSettings)
                        .ifPresent(x -> result.put("enabled", x.isEnabled()));
                break;
            case DELIVERY_VAT:
                commerceSettings.map(TurboCommerceSettings::getDeliverySection)
                        .ifPresent(x -> result.put("deliveryVat", x.getVat()));
                break;
            case DEFAULT_VAT:
                commerceSettings.map(TurboCommerceSettings::getPaymentsSettings)
                        .ifPresent(x -> result.put("defaultVat", x.getDefaultVat()));
                break;
            default:
                throw new IllegalStateException("unexpected type " + type.name());
        }
        return result;
    }


    public static class Request extends AbstractUserVerifiedHostRequest {
    }

    @Value
    public static class NormalResponse implements ActionResponse.NormalResponse {
        Map<PaymentSettingsType, List<PaymentState>> paymentInfo;
    }

    @Data
    @RequiredArgsConstructor
    private static class PaymentState {
        private String login;
        private final long userId;
        private final DateTime updateDate;
        private final Map<String, Object> paymentChanges;
    }

    private static class PaymentChange {
        enum Action {
            ADD, REMOVE, CHANGE
        }

        @NonNull
        public static Map<String, Object> computeChanges(Map<String, Object> oldSettings,
                                                         Map<String, Object> newSettings,
                                                         PaymentSettingsType type) {
            boolean hasChanges = false;
            boolean oldChanges = false;
            boolean newChanges = false;
            String key;
            switch (type) {
                case PAYMENT_INFO:
                    key = "token";
                    break;
                case ENABLED:
                    key = "enabled";
                    break;
                case DEFAULT_VAT:
                    key = "defaultVat";
                    break;
                case DELIVERY_VAT:
                    key = "deliveryVat";
                    break;
                default:
                    throw new IllegalStateException("unexpected type " + type.name());
            }

            Object oldValue = oldSettings.get(key);
            Object newValue = newSettings.get(key);

            if (oldValue instanceof String) {
                oldValue = StringUtils.trimToNull((String)oldValue);
            }
            if (newValue instanceof String) {
                newValue = StringUtils.trimToNull((String)newValue);
            }

            if (!Objects.equals(oldValue, newValue)) {
                hasChanges = true;

                newChanges = oldValue == null;
                oldChanges = newValue == null;
            }

            if (!hasChanges) {
                return Collections.emptyMap();
            }

            if (newChanges) {
                return add(newSettings);
            } else if (oldChanges) {
                return remove(oldSettings);
            } else {
                return change(oldSettings, newSettings);
            }
        }

        private static Map<String, Object> change(Map<String, Object> oldSetting, Map<String, Object> newSetting) {
            Map<String, Object> results = new HashMap<>();
            for (var key : Sets.union(oldSetting.keySet(), newSetting.keySet())) {
                results.put("old" + StringUtils.capitalize(key), oldSetting.get(key));
                results.put("new" + StringUtils.capitalize(key), newSetting.get(key));
            }
            results.put("action", Action.CHANGE);
            return results;
        }

        private static Map<String, Object> remove(Map<String, Object> oldSetting) {
            Map<String, Object> results = new HashMap<>();
            for (var key : oldSetting.keySet()) {
                results.put("old" + StringUtils.capitalize(key), oldSetting.get(key));
            }
            results.put("action", Action.REMOVE);
            return results;
        }

        private static Map<String, Object> add(Map<String, Object> newSetting) {
            Map<String, Object> results = new HashMap<>();
            for (var key : newSetting.keySet()) {
                results.put("new" + StringUtils.capitalize(key), newSetting.get(key));
            }
            results.put("action", Action.ADD);
            return results;
        }
    }

    private enum PaymentSettingsType {
        PAYMENT_INFO,
        ENABLED,
        DEFAULT_VAT,
        DELIVERY_VAT
    }
}
