package de.htwsaar.webshop.service.impl;

import de.htwsaar.webshop.model.ArticleCategory;
import de.htwsaar.webshop.model.CatMonthModel;
import de.htwsaar.webshop.model.OrderStatus;
import de.htwsaar.webshop.repository.entities.OrderItem;
import de.htwsaar.webshop.service.ArticleService;
import de.htwsaar.webshop.service.OrderService;
import de.htwsaar.webshop.service.StatisticsService;
import de.htwsaar.webshop.util.TimeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.TreeMap;
import java.util.function.BinaryOperator;
import java.util.function.Function;

@Service
@Slf4j
public class StatisticsServiceImpl implements StatisticsService {
    private final OrderService orderService;
    private final ArticleService articleService;

    @Autowired
    public StatisticsServiceImpl(OrderService orderService, ArticleService articleService) {
        this.orderService = orderService;
        this.articleService = articleService;
    }

    //returns Map<unix milli timestamp, Map<Category, T>>
    private <T extends Number> CatMonthModel<T> getMonthCategoryMap(Function<OrderItem, T> mappingFunction,
                                                                    BinaryOperator<T> reduceFunction,
                                                                    T defaultValue) {
        Map<String, Map<Long, T>> map = new TreeMap<>();
        for (ArticleCategory value : ArticleCategory.values()) {
            map.put(value.loc, new TreeMap<>());
        }
        orderService.getTimeSortedOrders(TimeUtil.nowMonthsAgo(12), 12).forEach( (k,v) -> {
            for (ArticleCategory value : ArticleCategory.values()) {
                map.get(value.loc).putIfAbsent(k, defaultValue);
            }
            v.forEach( (order) ->
                order.getOrderItems().forEach((item) -> {
                    map.putIfAbsent(item.getArticle().getCategory(), new TreeMap<>());
                    map.get(item.getArticle().getCategory()).putIfAbsent(k, defaultValue);
                    map.get(item.getArticle().getCategory()).computeIfPresent(k, (timestamp,left ) -> reduceFunction.apply(left, mappingFunction.apply(item)));
                })
            );
        });
        return new CatMonthModel<>(map);
    }

    @Override
    public CatMonthModel<Integer> getSalesVolume() {
        return getMonthCategoryMap(OrderItem::getAmount, Integer::sum, 0);
    }

    @Override
    public CatMonthModel<Integer> getSalesRevenue() {
        return getMonthCategoryMap(item -> item.getArticle().getPrice100(), Integer::sum, 0);
    }

    @Override
    public Map<OrderStatus, Integer> getOrderStatus() {
        Map<OrderStatus, Integer> map = new TreeMap<>();
        orderService.findAll().forEach(order -> {
            map.putIfAbsent(order.getStatus(), 0);
            map.computeIfPresent(order.getStatus(), (k,v) -> ++v);
        });
        return map;
    }

    @Override
    public Map<String, Map<Integer, Integer>> getStockPercent() {
        Map<String, Map<Integer, Integer>> map = new TreeMap<>();
        for (ArticleCategory value : ArticleCategory.values()) {
            map.putIfAbsent(value.loc, new TreeMap<>());
            log.info("Value {}", value.loc);
        }
        articleService.findAll().forEach(article -> {
            int percent = (int) Math.floor(((1.0d * article.getStock() / article.getStockExpected()) * 100) / 10) * 10;
            log.info("Stock percent: {} {}", article.getUuid(), percent);
            for (ArticleCategory value : ArticleCategory.values()) {
                if (article.getCategory().equalsIgnoreCase(value.loc)) {
                    log.info("Fixing Article Category {} to {}", article.getCategory(), value.loc);
                    article.setCategory(value.loc);
                    articleService.update(article);
                    break;
                }
            }
            map.get(article.getCategory()).putIfAbsent(percent, 0);
            map.get(article.getCategory()).computeIfPresent(percent, (k,v) -> ++v);
        });
        return map;
    }
}
