package ru.soune.nocopy.service.statistic;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.soune.nocopy.dto.statistic.SubscriberStatisticResponse;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.tarif.TariffTimeTerm;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.repository.TariffInfoRepository;
import ru.soune.nocopy.repository.UserRepository;

import java.util.*;

@Service
@RequiredArgsConstructor
@Slf4j
public class SubscriberStatisticService {

    private final TariffInfoRepository tariffInfoRepository;
    private final UserRepository userRepository;

    public SubscriberStatisticResponse getSubscriberStatistics() {
        return SubscriberStatisticResponse.builder()
                .monthly(buildPeriodStatistics(TariffTimeTerm.MONTHLY))
                .yearly(buildPeriodStatistics(TariffTimeTerm.YEAR))
                .build();
    }

    private SubscriberStatisticResponse.PeriodStatistics buildPeriodStatistics(TariffTimeTerm term) {
        Long currentActive = tariffInfoRepository.getCurrentActiveSubscriptions(term);

        List<TariffInfo> allTariffs = tariffInfoRepository.getAllTariffInfosForAnalysis(term);

        Map<Long, List<TariffInfo>> tariffsByUser = groupTariffsByUser(allTariffs);

        Long firstTime = countFirstTimeSubscriptions(tariffsByUser);
        Long renewals = countRenewals(tariffsByUser);
        UpDownGradeResult gradeResult = analyzeUpgradesAndDowngrades(tariffsByUser);

        return SubscriberStatisticResponse.PeriodStatistics.builder()
                .currentActiveSubscriptions(currentActive != null ? currentActive : 0L)
                .firstTimeSubscriptions(firstTime)
                .renewals(renewals)
                .upgrades(gradeResult.upgrades)
                .downgrades(gradeResult.downgrades)
                .build();
    }

    private Map<Long, List<TariffInfo>> groupTariffsByUser(List<TariffInfo> tariffs) {
        Map<Long, List<TariffInfo>> result = new HashMap<>();

        for (TariffInfo ti : tariffs) {
            Optional<User> userOpt = userRepository.findByPersonalTariffInfoId(ti.getId());
            if (userOpt.isPresent()) {
                Long userId = userOpt.get().getId();
                result.computeIfAbsent(userId, k -> new ArrayList<>()).add(ti);
            }
        }

        return result;
    }

    private Long countFirstTimeSubscriptions(Map<Long, List<TariffInfo>> tariffsByUser) {
        return (long) tariffsByUser.size();
    }

    private Long countRenewals(Map<Long, List<TariffInfo>> tariffsByUser) {
        return tariffsByUser.values().stream()
                .filter(list -> list.size() > 1)
                .mapToLong(list -> list.size() - 1)
                .sum();
    }

    private UpDownGradeResult analyzeUpgradesAndDowngrades(Map<Long, List<TariffInfo>> tariffsByUser) {
        long upgrades = 0L;
        long downgrades = 0L;

        for (List<TariffInfo> userTariffs : tariffsByUser.values()) {
            if (userTariffs.size() < 2) {
                continue;
            }

            userTariffs.sort(Comparator.comparing(TariffInfo::getStartTariff,
                    Comparator.nullsLast(Comparator.naturalOrder())));

            for (int i = 1; i < userTariffs.size(); i++) {
                TariffInfo previous = userTariffs.get(i - 1);
                TariffInfo current = userTariffs.get(i);

                if (previous.getTariff() == null || current.getTariff() == null) {
                    continue;
                }

                double previousPrice = previous.getTariff().getPrice();
                double currentPrice = current.getTariff().getPrice();

                if (currentPrice > previousPrice) {
                    upgrades++;
                } else if (currentPrice < previousPrice) {
                    downgrades++;
                }
            }
        }

        return new UpDownGradeResult(upgrades, downgrades);
    }

    private static class UpDownGradeResult {
        final Long upgrades;
        final Long downgrades;

        UpDownGradeResult(Long upgrades, Long downgrades) {
            this.upgrades = upgrades;
            this.downgrades = downgrades;
        }
    }
}
