package ru.yandex.webmaster3.storage.niche2;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.util.FNVHash;
import ru.yandex.webmaster3.storage.searchquery.RegionInclusion;
import ru.yandex.webmaster3.storage.searchquery.dao.QueryStatisticsFiltersService;
import ru.yandex.webmaster3.storage.util.clickhouse2.AbstractClickhouseDao;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseQueryContext;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.Clause;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.QueryBuilder;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.RawStatement;

import static ru.yandex.webmaster3.core.util.IdUtils.urlToHostId;
import static ru.yandex.webmaster3.core.util.WwwUtil.cutWWWAndM;

@Repository
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Slf4j
public class Niche2RivalsCHDao extends AbstractClickhouseDao {
    public static final String TABLE_NAME = "rival_report";
    private static final List<String> RIVAL_TYPES = List.of("bookmaker",
            "business",
            "classified",
            "community",
            "content",
            "ecommerce",
            "itself",
            "media",
            "online-games",
            "other",
            "porno",
            "portal",
            "service",
            "social",
            "undef",
            "video");
    private static final List<String> REAL_RIVAL_TYPES =
            RIVAL_TYPES.stream().filter(x -> !List.of("itself", "undef").contains(x)).toList();
    private final int CLUSTER_EXAMPLES_LOWER_BOUND = 3;

    private final int SIGNIFICANT_RIVAL_COUNTER_IN_CLUSTER = 3;

    private final int RIVAL_EXAMPLES_THRESHOLD = 10;
    private final double SIGNIFICANT_RIVAL_IN_CLUSTER = 0.1;

    private final QueryStatisticsFiltersService mdbQueryStatisticsFiltersService;

    private Clause addFilters(Clause statement, LocalDate dateFrom, LocalDate dateTo, Integer[] regionIds) {
        statement = statement
                .and(QueryBuilder.gte(F.DATE, dateFrom))
                .and(QueryBuilder.lte(F.DATE, dateTo))
                .and(new RawStatement(mdbQueryStatisticsFiltersService.getRegionFilter(RegionInclusion.INCLUDE_ALL,
                        regionIds)));
        return statement;
    }

    private ClickhouseQueryContext.Builder chContext(WebmasterHostId hostId) {
        int shardsCount = getClickhouseServer().getShardsCount();
        int shard = (int) FNVHash.hash64Mod(cutWWWAndM(hostId), shardsCount);
        return ClickhouseQueryContext.useDefaults().setHost(getClickhouseServer().pickAliveHostOrFail(shard));
    }

    public List<Integer> getRegions(WebmasterHostId hostId, String groupId) {
        var st = QueryBuilder.select("distinct " + F.REGION_ID)
                .from(AbstractClickhouseDao.DB_WEBMASTER3_NICHE, TABLE_NAME)
                .where(QueryBuilder.eq(F.DOMAIN, cutWWWAndM(hostId)));
        if (groupId != null) {
            st = st.and(QueryBuilder.eq(F.GROUP_ID, groupId));
        }
        return getClickhouseServer().queryAll(chContext(hostId), st.toString(), chRow -> (int) chRow.getLong(0));
    }

    public List<ClusterPopularityHistory> getHostVisibilityInfo(WebmasterHostId hostId, String groupId,
                                                                LocalDate dateFrom, LocalDate dateTo,
                                                                Integer[] regionIds) {
        return getClustersVisibilityInfo(hostId, groupId, dateFrom, dateTo, List.of("itself"), regionIds);
    }

    private LocalDate getLastDate() {
        return getClickhouseServer().queryOne(
                        QueryBuilder.select(QueryBuilder.as(QueryBuilder.max(F.DATE), F.DATE).toString())
                                .from(DB_WEBMASTER3_NICHE, TABLE_NAME)
                                .toString(),
                        chRow -> chRow.getLocalDate(F.DATE))
                .get();
    }

    public List<ClusterPopularityHistory> getRivalsVisibilityInfo(WebmasterHostId hostId, String groupId,
                                                                  Integer[] regionIds) {
        var lastDate = getLastDate();
        return getClustersVisibilityInfo(hostId, groupId, lastDate, lastDate, RIVAL_TYPES, regionIds);
    }


    public Map<String, List<WebmasterHostId>> getRivalsExamples(WebmasterHostId hostId, String groupId,
                                                                Integer[] regionIds) {
        var domain = cutWWWAndM(hostId);
        var lastDate = getLastDate();
        var st = addFilters(
                QueryBuilder
                        .selectDistinct(F.DATE,
                                F.RIVAL_TYPE,
                                F.RIVAL,
                                QueryBuilder.as(QueryBuilder.max(F.VISIBILITY), F.VISIBILITY).toString())
                        .from(AbstractClickhouseDao.DB_WEBMASTER3_NICHE, TABLE_NAME)
                        .where(QueryBuilder.eq(F.DOMAIN, domain))
                        .and(QueryBuilder.eq(F.GROUP_ID, groupId))
                        .and(QueryBuilder.in(F.RIVAL_TYPE, REAL_RIVAL_TYPES)),
                lastDate,
                lastDate,
                regionIds)
                .groupBy(F.DATE, F.RIVAL_TYPE, F.RIVAL);
        var rows = getClickhouseServer()
                .queryAll(chContext(hostId), st, chRow -> new ExamplesRawRow(chRow.getString(F.RIVAL_TYPE),
                        chRow.getLocalDate(F.DATE),
                        urlToHostId(chRow.getString(F.RIVAL)),
                        chRow.getDouble(F.VISIBILITY)));

        Map<String, List<WebmasterHostId>> res = new HashMap<>();
        rows.stream().collect(Collectors.groupingBy(x -> x.cluster)).forEach((cluster, data) -> {
            if (data.size() >= CLUSTER_EXAMPLES_LOWER_BOUND && data.stream().map(x -> x.visibility).anyMatch(x -> x > SIGNIFICANT_RIVAL_IN_CLUSTER)) {
                res.put(cluster,
                        new HashSet<>(data.stream().sorted((x, y) -> Double.compare(y.visibility,
                                x.visibility)).limit(RIVAL_EXAMPLES_THRESHOLD).collect(Collectors.toList()))
                                .stream()
                                .map(x -> x.example)
                                .collect(Collectors.toList()));
            }
        });
        return res;
    }

    public List<ClusterPopularityHistory> getClustersVisibilityInfo(WebmasterHostId hostId, String groupId,
                                                                    LocalDate dateFrom, LocalDate dateTo,
                                                                    List<String> clusters, Integer[] regionIds) {
        if (clusters == null) {
            return List.of();
        }
        var domain = cutWWWAndM(hostId);
        var st = addFilters(
                QueryBuilder
                        .select(F.DATE,
                                F.RIVAL_TYPE,
                                QueryBuilder.sum(F.VISIBILITY) + " as " + F.VISIBILITY)
                        .from(AbstractClickhouseDao.DB_WEBMASTER3_NICHE, TABLE_NAME)
                        .where(QueryBuilder.eq(F.DOMAIN, domain))
                        .and(QueryBuilder.eq(F.GROUP_ID, groupId))
                        .and(QueryBuilder.in(F.RIVAL_TYPE, clusters)),
                dateFrom,
                dateTo,
                regionIds)
                .groupBy(F.DATE, F.RIVAL_TYPE, F.RIVAL);
        var rows = getClickhouseServer()
                .queryAll(
                        chContext(hostId),
                        st,
                        chRow -> new PopularityRawRow(chRow.getString(F.RIVAL_TYPE), chRow.getLocalDate(F.DATE),
                                chRow.getDouble(F.VISIBILITY))
                );
        List<ClusterPopularityHistory> res = new ArrayList<>();
//        нужный формат на выходе -- [{кластер, [date, visibility]}]
//        за день у кластера видимость считается как среднее по топ3
        rows
                .stream()
                .collect(Collectors.groupingBy(x -> x.cluster))
                .forEach(
                        (cluster, clusterData) -> { // группируем по кластеру
                            List<PopularityRecord> dateToVisibility = new ArrayList<>();
                            clusterData.stream().collect(Collectors.groupingBy(x -> x.date))
                                    .forEach((date, dateData) -> { // группируем по дате и считаем нужный агрегат
                                        double topNAvgVisibility = dateData
                                                .stream()
                                                .sorted(
                                                        (x, y) ->
                                                                Double.compare(y.visibility, x.visibility)
                                                )
                                                .limit(SIGNIFICANT_RIVAL_COUNTER_IN_CLUSTER)
                                                .mapToDouble(x -> x.visibility)
                                                .average()
                                                .orElse(0);
                                        dateToVisibility.add(new PopularityRecord(date, topNAvgVisibility));
                                    });
                            res.add(new ClusterPopularityHistory(cluster, dateToVisibility));
                        }
                );

        clusters.forEach(cluster -> {
            if (res.stream().noneMatch(x -> Objects.equals(x.cluster, cluster))) {
                res.add(new ClusterPopularityHistory(cluster, new ArrayList<>()));
            }
        });
        return res;
    }

    private record PopularityRawRow(String cluster, LocalDate date, double visibility) {
    }

    private record ExamplesRawRow(String cluster, LocalDate date, WebmasterHostId example, double visibility) {
    }

    public record PopularityRecord(LocalDate date, double visibility) {
    }

    public record ClusterPopularityHistory(String cluster, List<PopularityRecord> history) {
    }

    public interface F {
        String DATE = "date";
        String DOMAIN = "domain";
        String RIVAL = "rival";
        String RIVAL_TYPE = "rival_type";
        String REGION_ID = "region_id";
        String GROUP_ID = "group_id";
        String VISIBILITY = "visibility";
    }
}
