package com.izouma.jmrh.service.stat;

import com.izouma.jmrh.config.Constants;
import com.izouma.jmrh.domain.stat.*;
import com.izouma.jmrh.enums.ResSnDType;
import com.izouma.jmrh.exception.BusinessException;
import com.izouma.jmrh.repo.*;
import com.izouma.jmrh.repo.stat.*;
import com.izouma.jmrh.utils.DateTimeUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.util.*;

@Service
@AllArgsConstructor
@Slf4j
public class DataCenterService {
    private OrgNumStatRepo              orgNumStatRepo;
    private ProjectNumStatRepo          projectNumStatRepo;
    private CityIndexStatRepo           cityIndexStatRepo;
    private BaseStatRepo                baseStatRepo;
    private TechTradeStatRepo           techTradeStatRepo;
    private ResourceSupplyAndDemandRepo resourceSupplyAndDemandRepo;
    private PostRepo                    postRepo;
    private CommentRepo                 commentRepo;
    private CityDataRepo                cityDataRepo;
    private CategoryRepo                categoryRepo;
    private ThresholdService            thresholdService;
    private OrgInfoRepo                 orgInfoRepo;
    private ProjectManagementRepo       projectManagementRepo;
    private ArticleRepo                 articleRepo;

    public void init() {
        baseStatRepo.deleteAll();
        cityIndexStatRepo.deleteAll();
        orgNumStatRepo.deleteAll();
        projectNumStatRepo.deleteAll();
        cityDataRepo.deleteAll();
        techTradeStatRepo.deleteAll();
        categoryRepo.deleteAll();

        Arrays.stream(Constants.cityDataCategories).parallel().forEach(category -> {
            categoryRepo.save(Category.builder().name(category).build());
        });

        thresholdService.init();

        Arrays.stream(Constants.cities).parallel().forEach(city -> {
            baseStatRepo.save(BaseStat.builder()
                    .city(city)
                    .num((int) (Math.random() * 100))
                    .build());
            cityIndexStatRepo.save(CityIndexStat.builder()
                    .city(city)
                    .cityIndex((int) (Math.random() * 100))
                    .build());
            for (int year = 2011; year <= 2021; year++) {
                for (int month = 1; month <= 12; month++) {
                    orgNumStatRepo.save(OrgNumStat.builder()
                            .city(city)
                            .year(year)
                            .month(month)
                            .num((long) (Math.random() * 100))
                            .build());
                    projectNumStatRepo.save(ProjectNumStat.builder()
                            .city(city)
                            .year(year)
                            .month(month)
                            .num((long) (Math.random() * 100))
                            .build());
                }
            }

            List<Category> categories = categoryRepo.findAll();

            categories.forEach(category -> {
                cityDataRepo.save(CityData.builder()
                        .city(city)
                        .category(category)
                        .num((int) (Math.random() * 100))
                        .build());
            });
        });

        for (int year = 2011; year <= 2021; year++) {
            for (int month = 1; month <= 12; month++) {
                techTradeStatRepo.save(TechTradeStat.builder()
                        .year(year)
                        .month(month)
                        .trade((int) (Math.random() * 100))
                        .consult((int) (Math.random() * 100))
                        .build());
            }
        }
    }

    public Object orgNum(String type) {
        if ("year".equals(type)) {
            Map<String, Object> map = new HashMap<>();
            Calendar calendar = Calendar.getInstance();
            List<String> labels = new ArrayList<>();
            List<Long> data = new ArrayList<>();
            for (int year = calendar.get(Calendar.YEAR) - 9; year <= calendar.get(Calendar.YEAR); year++) {
                labels.add(String.valueOf(year));
                data.add(orgNumStatRepo.findByYear(year).stream().mapToLong(OrgNumStat::getNum).sum());
            }
            map.put("labels", labels);
            map.put("data", data);
            return map;
        } else if ("month".equals(type)) {
            Map<String, Object> map = new HashMap<>();
            List<String> labels = new ArrayList<>();
            List<Long> data = new ArrayList<>();
            LocalDate date = LocalDate.now().plusMonths(-12);
            for (int i = 1; i <= 12; i++) {
                LocalDate date1 = date.plusMonths(i);
                int year = date1.get(ChronoField.YEAR);
                int month = date1.get(ChronoField.MONTH_OF_YEAR);
                labels.add(String.valueOf(month));
                data.add(orgNumStatRepo.findByYearAndMonth(year, month)
                        .stream().mapToLong(OrgNumStat::getNum).sum());
            }
            map.put("labels", labels);
            map.put("data", data);
            return map;
        }
        throw new BusinessException("unknown type");
    }

    public Object cityOrgNum() {
        Map<String, Object> map = new HashMap<>();
        List<String> labels = Arrays.asList(Constants.cities);
        List<Long> data = new ArrayList<>();
        LocalDate date = LocalDate.now();
        int year = date.get(ChronoField.YEAR);
        int month = date.get(ChronoField.MONTH_OF_YEAR);
        for (String city : labels) {
            data.add(orgNumStatRepo.findFirstByYearAndMonthAndCity(year, month, city)
                    .map(OrgNumStat::getNum).orElse(0L));
        }
        map.put("labels", labels);
        map.put("data", data);
        return map;
    }

    public Object cityProjectNum() {
        Map<String, Object> map = new HashMap<>();
        List<String> labels = Arrays.asList(Constants.cities);
        List<Long> data = new ArrayList<>();
        LocalDate date = LocalDate.now();
        int year = date.get(ChronoField.YEAR);
        int month = date.get(ChronoField.MONTH_OF_YEAR);
        for (String city : labels) {
            data.add(projectNumStatRepo.findFirstByYearAndMonthAndCity(year, month, city)
                    .map(ProjectNumStat::getNum).orElse(0L));
        }
        map.put("labels", labels);
        map.put("data", data);
        return map;
    }

    public Object cityIndexStat() {
        Map<String, Object> map = new HashMap<>();
        List<String> labels = Arrays.asList(Constants.cities);
        List<Integer> data = new ArrayList<>();
        for (String city : labels) {
            data.add(cityIndexStatRepo.findByCity(city).map(CityIndexStat::getCityIndex).orElse(0));
        }
        map.put("labels", labels);
        map.put("data", data);
        return map;
    }

    public Object baseStat() {
        Map<String, Object> map = new HashMap<>();
        List<String> labels = Arrays.asList(Constants.cities);
        List<Integer> data = new ArrayList<>();
        for (String city : labels) {
            data.add(baseStatRepo.findByCity(city).map(BaseStat::getNum).orElse(0));
        }
        map.put("labels", labels);
        map.put("data", data);
        return map;
    }

    public Object techTradeStat() {
        Map<String, Object> map = new HashMap<>();
        List<String> labels = new ArrayList<>();
        List<Integer> tradeData = new ArrayList<>();
        List<Integer> consultData = new ArrayList<>();
        LocalDate date = LocalDate.now().plusMonths(-12);
        for (int i = 1; i <= 12; i++) {
            LocalDate date1 = date.plusMonths(i);
            int year = date1.get(ChronoField.YEAR);
            int month = date1.get(ChronoField.MONTH_OF_YEAR);
            labels.add(String.valueOf(month));
            Optional<TechTradeStat> techTradeStat = techTradeStatRepo.findByYearAndMonth(year, month);
            tradeData.add(techTradeStat.map(TechTradeStat::getTrade).orElse(0));
            consultData.add(techTradeStat.map(TechTradeStat::getConsult).orElse(0));
        }
        map.put("labels", labels);
        map.put("tradeData", tradeData);
        map.put("consultData", consultData);
        return map;
    }

    public Object sndStat() {
        Map<String, Object> map = new HashMap<>();
        map.put("供应总数", resourceSupplyAndDemandRepo
                .countByTypeIn(Arrays
                        .asList(ResSnDType.RES_SUPPLY, ResSnDType.PRODUCT_SUPPLY, ResSnDType.TECH_SUPPLY)));
        map.put("需求总数", resourceSupplyAndDemandRepo
                .countByTypeIn(Arrays
                        .asList(ResSnDType.FINANCING_DEMAND, ResSnDType.PRODUCT_DEMAND, ResSnDType.TECH_DEMAND)));
        map.put("互动总数", postRepo.count() + commentRepo.count());
        return map;
    }

    public Object cityData(Long categoryId) {
        return cityDataRepo.findCityDataByCategoryId(categoryId);
    }

    public Object numStat(YearMonth start, YearMonth end, String type) {
        Map<String, Object> map = new HashMap<>();
        List<String> labels = new ArrayList<>();
        List<Long> data = new ArrayList<>();
        for (int i = 0; i < ChronoUnit.MONTHS.between(start, end); i++) {
            YearMonth yearMonth = start.plusMonths(i);
            labels.add(DateTimeUtils.format(yearMonth, "yyyy-MM"));
            LocalDateTime statTime = yearMonth.atDay(1).atStartOfDay();
            LocalDateTime endTime = yearMonth.atEndOfMonth().atTime(LocalTime.of(23, 59, 59));
            switch (type) {
                case "org":
                    data.add(orgInfoRepo.countByCreatedAtBetween(statTime, endTime));
                    break;
                case "project":
                    data.add(projectManagementRepo.countByCreatedAtBetween(statTime, endTime));
                    break;
                case "product":
                    data.add(resourceSupplyAndDemandRepo.countByCreatedAtBetweenAndTypeIn(statTime, endTime, Arrays
                            .asList(ResSnDType.PRODUCT_SUPPLY)));
                    break;
                case "tech":
                    data.add(resourceSupplyAndDemandRepo.countByCreatedAtBetweenAndTypeIn(statTime, endTime, Arrays
                            .asList(ResSnDType.TECH_SUPPLY)));
                    break;
                case "news":
                    data.add(articleRepo.countByCreatedAtBetween(statTime, endTime));
                    break;
            }
        }
        map.put("labels", labels);
        map.put("data", data);
        return map;
    }
}
