package ru.yandex.webmaster3.storage.niche;

import java.util.List;
import java.util.function.Function;

import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import ru.yandex.autodoc.common.doc.annotation.Description;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.searchquery.OrderDirection;
import ru.yandex.webmaster3.core.util.FNVHash;
import ru.yandex.webmaster3.storage.searchquery.DeviceType;
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.CHRow;
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.Join;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.OrderBy;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.QueryBuilder;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.RawStatement;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.Statement;

/**
 * ishalaru
 * 30.06.2021
 **/
@Repository
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class NicheRivalQueriesReportCHDao extends AbstractClickhouseDao {
    public static final String TABLE_NAME = "rival_queries_report3";

    private final QueryStatisticsFiltersService mdbQueryStatisticsFiltersService;

    private Clause addFilters(Clause statement, LocalDate dateFrom, LocalDate dateTo, Integer[] regionIds, DeviceType deviceType, QuerySource querySource) {
        statement = statement
                .and(QueryBuilder.gte(F.DATE, dateFrom))
                .and(QueryBuilder.lte(F.DATE, dateTo))
                .and(getDeviceFilter(deviceType))
                .and(new RawStatement(mdbQueryStatisticsFiltersService.getRegionFilter(RegionInclusion.INCLUDE_ALL, regionIds)));
        if (querySource != null && querySource != QuerySource.ALL) {
            statement = statement.and(QueryBuilder.eq(F.QUERY_SOURCE, querySource.value()));
        }
        return statement;
    }

    public List<RivalInfo> getRivals(WebmasterHostId hostId, LocalDate dateFrom, LocalDate dateTo, Integer[] regionIds,
                                     DeviceType deviceType, QuerySource querySource, SortField orderBy, OrderDirection orderDirection) {
        int periodDays = Days.daysBetween(dateFrom, dateTo).getDays();
        LocalDate dateToBefore = dateFrom.minusDays(1);
        LocalDate dateFromBefore = dateToBefore.minusDays(periodDays);

        var stPrevTotalPopularity = addFilters(
                QueryBuilder.select("sum(" + F.POPULARITY + ")")
                        .from(AbstractClickhouseDao.DB_WEBMASTER3_NICHE, TABLE_NAME)
                        .where(QueryBuilder.eq(F.HOST_ID, hostId))
                        .and(QueryBuilder.eq(F.RIVAL, "")),
                dateFromBefore,
                dateToBefore,
                regionIds,
                deviceType,
                querySource
        );

        var stCurrTotalPopularity = addFilters(
                QueryBuilder.select("sum(" + F.POPULARITY + ")")
                        .from(AbstractClickhouseDao.DB_WEBMASTER3_NICHE, TABLE_NAME)
                        .where(QueryBuilder.eq(F.HOST_ID, hostId))
                        .and(QueryBuilder.eq(F.RIVAL, "")),
                dateFrom,
                dateTo,
                regionIds,
                deviceType,
                querySource
        );


        var and = addFilters(QueryBuilder.select(F.RIVAL,
                "sum(if(" + QueryBuilder.lte(F.DATE, dateToBefore) + ", " + F.POPULARITY + ", 0.0)) / (" + stPrevTotalPopularity + ") as " + F.PREV_VISIBILITY,
                "sum(if(" + QueryBuilder.gte(F.DATE, dateFrom) + ", " + F.POPULARITY + ", 0.0)) / (" + stCurrTotalPopularity + ") as " + F.CURR_VISIBILITY,
                "(" + F.CURR_VISIBILITY + " - " + F.PREV_VISIBILITY + ") as " + F.VISIBILITY_DELTA)
                        .from(AbstractClickhouseDao.DB_WEBMASTER3_NICHE, TABLE_NAME)
                        .where(QueryBuilder.eq(F.HOST_ID, hostId))
                        .and(QueryBuilder.ne(F.RIVAL, "")),
                dateFromBefore,
                dateTo,
                regionIds,
                deviceType,
                querySource
        );
        var st = and
                .groupBy(F.RIVAL)
                .orderBy(orderBy.field, orderDirection == OrderDirection.ASC ? OrderBy.Direction.ASC : OrderBy.Direction.DESC);
        return getClickhouseServer().queryAll(chContext(hostId), st, chRow -> {
            WebmasterHostId rivalHostId = chRow.getHostId(F.RIVAL);
            Double currVisibility = chRow.getDouble(F.CURR_VISIBILITY);
            Double visibilityDelta = chRow.getDouble(F.VISIBILITY_DELTA);
            return new RivalInfo(rivalHostId, currVisibility, visibilityDelta);
        });
    }

    public long getRivalsCount(WebmasterHostId hostId, LocalDate dateFrom, LocalDate dateTo, Integer[] regionIds, DeviceType deviceType, QuerySource querySource) {
        var and = addFilters(
                QueryBuilder.select("count(distinct " + F.RIVAL + ")")
                        .from(AbstractClickhouseDao.DB_WEBMASTER3_NICHE, TABLE_NAME)
                        .where(QueryBuilder.eq(F.HOST_ID, hostId))
                        .and(QueryBuilder.ne(F.RIVAL, "")),
                dateFrom,
                dateTo,
                regionIds,
                deviceType,
                querySource
        );

        return getClickhouseServer().queryOne(chContext(hostId), and, chRow -> chRow.getLongUnsafe(0)).orElse(0L);
    }

    public List<RivalDateInfo> getRivalHistory(WebmasterHostId hostId, LocalDate dateFrom, LocalDate dateTo,
                                               List<WebmasterHostId> rivals, Integer[] regionIds, DeviceType deviceType,
                                               QuerySource source) {
        var totalPopularity = addFilters(
            QueryBuilder.select(F.DATE, "sum(" + F.POPULARITY + ") as " + F.POPULARITY)
                    .from(AbstractClickhouseDao.DB_WEBMASTER3_NICHE, TABLE_NAME)
                    .where(QueryBuilder.eq(F.HOST_ID, hostId))
                    .and(QueryBuilder.eq(F.RIVAL, "")),
                dateFrom,
                dateTo,
                regionIds,
                deviceType,
                source
        ).groupBy(F.DATE);

        var popularity = addFilters(
                QueryBuilder.select(F.DATE, F.RIVAL, "sum(" + F.POPULARITY + ") as " + F.POPULARITY)
                        .from(AbstractClickhouseDao.DB_WEBMASTER3_NICHE, TABLE_NAME)
                        .where(QueryBuilder.eq(F.HOST_ID, hostId))
                        .and(QueryBuilder.in(F.RIVAL, rivals)),
                dateFrom,
                dateTo,
                regionIds,
                deviceType,
                source
        ).groupBy(F.DATE, F.RIVAL);

        var st = QueryBuilder.select(F.DATE, F.RIVAL, "r." + F.POPULARITY + " / total." + F.POPULARITY + " as " + F.VISIBILITY)
                .from(popularity)
                .join(Join.Strictness.ANY, Join.Type.LEFT, totalPopularity, F.DATE, "r", "total")
                .orderBy(F.DATE, OrderBy.Direction.ASC);

        return getClickhouseServer().queryAll(chContext(hostId), st.toString(), QUERY_INFO_MAPPER);
    }

    private static final Function<CHRow, RivalDateInfo> QUERY_INFO_MAPPER = chRow ->
            new RivalDateInfo(chRow.getLocalDate(F.DATE),
                    chRow.getHostId(F.RIVAL),
                    chRow.getDouble(F.VISIBILITY)
            );

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

    // TODO злобный костыль, убрать после UX
    private static Statement getDeviceFilter(DeviceType deviceType) {
        if (deviceType == DeviceType.MOBILE) {
            deviceType = DeviceType.PAD;
        } else if (deviceType == null) {
            deviceType = DeviceType.ALL_DEVICES;
        }
        return deviceType.getQueryFilter();
    }

    @Value
    public static class RivalDateInfo {
        LocalDate date;
        WebmasterHostId rivalHostId;
        Double visibility;
    }

    @Value
    public static class RivalInfo {
        WebmasterHostId hostId;
        Double visibility;
        Double visibilityDelta;

        @Description("Имя хоста без схемы и пути")
        public String getHost() {
            return hostId.getReadableHostname();
        }
    }

    @AllArgsConstructor
    public enum SortField {
        RIVAL(F.RIVAL),
        VISIBILITY(F.CURR_VISIBILITY),
        VISIBILITY_DELTA(F.VISIBILITY_DELTA),
        ;

        private final String field;
    }

    public interface F {
        String DATE = "date";
        String HOST_ID = "host_id";
        String RIVAL = "rival";
        String REGION_ID = "region_id";
        String DEVICE_TYPE = "device_type";
        String QUERY_SOURCE = "query_source";
        String VISIBILITY = "visibility";
        String POPULARITY = "popularity";
        String CURR_VISIBILITY = "curr_visibility";
        String PREV_VISIBILITY = "prev_visibility";
        String VISIBILITY_DELTA = "visibility_delta";
    }
}
