package ru.yandex.webmaster3.storage.searchurl.samples.dao;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.sitestructure.RawSearchUrlStatusEnum;
import ru.yandex.webmaster3.storage.clickhouse.table.SearchUrlsTable;
import ru.yandex.webmaster3.storage.searchurl.samples.data.RawSearchUrlSample;
import ru.yandex.webmaster3.storage.searchurl.samples.data.RawUrlStatusInfo;
import ru.yandex.webmaster3.storage.util.clickhouse2.AbstractClickhouseDao;
import ru.yandex.webmaster3.storage.util.clickhouse2.CHRow;
import ru.yandex.webmaster3.storage.util.clickhouse2.condition.Condition;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.GroupableLimitableOrderable;
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.Where;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.Case;

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

/**
 * @author avhaliullin
 */
public class SearchUrlSamplesCHDao extends AbstractClickhouseDao {
    public List<Pair<RawSearchUrlStatusEnum, Integer>> getStatuses(SearchUrlsTable table, WebmasterHostId hostId, Condition filters) {
        Where st = QueryBuilder.selectDistinct(F.STATUS.getName(), F.EX_HTTP_CODE.getName())
                .from(getTable(table, hostId))
                .where(QueryBuilder.eq(F.HOST_ID.getName(), hostId));
        st = filter(st, filters);
        return getClickhouseServer().queryAll(table.chContext(getClickhouseServer(), hostId), st.toString(),
                row -> Pair.of(getStatus(row), row.getInt(F.EX_HTTP_CODE.getName())));
    }

    public long getSamplesCount(SearchUrlsTable table, WebmasterHostId hostId, Condition filters) {
        Where st = QueryBuilder.select()
                .countAll()
                .from(getTable(table, hostId))
                .where(QueryBuilder.eq(F.HOST_ID.getName(), hostId));

        st = filter(st, filters);

        return getClickhouseServer().queryOne(table.chContext(getClickhouseServer(), hostId), st.toString(),
                r -> r.getLongUnsafe(0)).orElse(0L);
    }


    public List<RawSearchUrlSample> getSamples(SearchUrlsTable table, WebmasterHostId hostId, Condition filters, long limitFrom, long limitSize) {
        Where st = getSampleWhereStatement(table, hostId);

        st = filter(st, filters);
        GroupableLimitableOrderable fst = st
                .orderBy(Arrays.asList(
                        Pair.of(F.LAST_ACCESS.getName(), OrderBy.Direction.DESC),
                        Pair.of(F.PATH.getName(), OrderBy.Direction.DESC)
                ))
                .limit((int) limitFrom, (int) limitSize);
        return getClickhouseServer().queryAll(table.chContext(getClickhouseServer(), hostId), fst.toString(), MAPPER);
    }

    private static final Function<CHRow,Pair<String,Long>> COUNT_MAPPER = chRow ->
            Pair.of(chRow.getString("la"),chRow.getLongUnsafe("cnt"));

    public List<Pair<String, Long>> countGroupingByLastAccess(SearchUrlsTable table, WebmasterHostId hostId, Condition filters){
        String groupField = "toDate(FROM_UNIXTIME(last_access))";
        String selectField = "toDate(FROM_UNIXTIME(last_access)) as la,count() as cnt";
        Where st = QueryBuilder.select(selectField)
                .from(getTable(table, hostId))
                .where(QueryBuilder.eq(SearchUrlFreshUrlSampleCHDao.F.HOST_ID, hostId));

        st = filter(st, filters);
        return getClickhouseServer().queryAll(table.chContext(getClickhouseServer(), hostId), st.groupBy(groupField).toString(),COUNT_MAPPER);
    }

    public Optional<RawSearchUrlSample> getSample(SearchUrlsTable table, WebmasterHostId hostId, String path) {

        Where st = getSampleWhereStatement(table, hostId);
        st = st.and(QueryBuilder.eq(F.PATH.getName(), path));

        return getClickhouseServer().queryOne(table.chContext(getClickhouseServer(), hostId), st.toString(), MAPPER);
    }

    private Where getSampleWhereStatement(SearchUrlsTable table, WebmasterHostId hostId) {
        return QueryBuilder.select(
                F.LAST_ACCESS.getName(),
                F.PATH.getName(),
                F.TITLE.getName(),
                F.STATUS.getName(),
                F.EX_REGION.getName(),
                F.EX_ADD_TIME.getName(),
                F.EX_BEAUTY_URL.getName(),
                F.EX_HTTP_CODE.getName(),
                F.EX_MAIN_HOST.getName(),
                F.EX_MAIN_PATH.getName(),
                F.EX_MAIN_REGION.getName(),
                F.EX_REDIR_TARGET.getName(),
                F.EX_REL_CANONICAL_TARGET.getName(),
                F.EX_IS_FAKE.getName(),
                F.EX_IS_INDEXED.getName(),
                F.EX_IS_TURBO.getName(),
                F.EX_IS_FROM_SITEMAP.getName(),
                F.EX_IS_SEARCHABLE.getName(),
                F.EX_TURBO_SOURCE_FLAGS.getName(),
                F.EX_VALID_FROM_METRIKA_LAST_ACCESS.getName(),
                F.EX_VALID_FROM_INDEXNOW_LAST_ACCESS.getName()

                ).from(getTable(table, hostId))
                .prewhere(QueryBuilder.eq(F.HOST_ID.getName(), hostId));

    }

    private static RawSearchUrlStatusEnum getStatus(CHRow row) {
        int statusInt = row.getInt(F.STATUS.name);
        RawSearchUrlStatusEnum status = RawSearchUrlStatusEnum.R.fromValueOrNull(statusInt);
        if (status == null) {
            status = RawSearchUrlStatusEnum.OTHER;
        }
        return status;
    }

    private static boolean getBoolean(CHRow row, F field) {
        return row.getInt(field.name) > 0;
    }

    private static Integer getNullableInt(CHRow row, F field) {
        int result = row.getInt(field.name);
        return result == 0 ? null : result;
    }

    private static String getNullableString(CHRow row, F field) {
        String result = row.getString(field.name);
        if (StringUtils.isEmpty(result)) {
            return null;
        } else {
            return result;
        }
    }

    private static Long getLong(CHRow row, F field) {
        return row.getLong(field.name);
    }

    private Where filter(Where st, Condition filters) {
        if (filters == null) {
            return st;
        }
        String filterStr = filters.toQuery();
        return st.and(new Case() {
            @Override
            public String toString() {
                return filterStr;
            }
        });
    }

    @NotNull
    private String getTable(SearchUrlsTable table, WebmasterHostId hostId) {
        return table.getFullTableName(getClickhouseServer(), hostId);
    }

    private static final Function<CHRow, RawSearchUrlSample> MAPPER = chRow -> {
        RawUrlStatusInfo statusInfo = new RawUrlStatusInfo(
                getStatus(chRow),
                getNullableString(chRow, F.EX_REGION),
                getNullableDate(chRow, F.EX_ADD_TIME.getName()),
                getNullableString(chRow, F.EX_BEAUTY_URL),
                getNullableInt(chRow, F.EX_HTTP_CODE),
                getNullableString(chRow, F.EX_MAIN_HOST),
                getNullableString(chRow, F.EX_MAIN_PATH),
                getNullableString(chRow, F.EX_MAIN_REGION),
                getNullableString(chRow, F.EX_REDIR_TARGET),
                getNullableString(chRow, F.EX_REL_CANONICAL_TARGET),
                getBoolean(chRow, F.EX_IS_FAKE),
                getBoolean(chRow, F.EX_IS_INDEXED),
                getBoolean(chRow, F.EX_IS_TURBO),
                getBoolean(chRow, F.EX_IS_FROM_SITEMAP),
                getBoolean(chRow, F.EX_IS_SEARCHABLE),
                chRow.getLong(F.EX_TURBO_SOURCE_FLAGS.getName()),
                getLong(chRow, F.EX_VALID_FROM_METRIKA_LAST_ACCESS),
                getLong(chRow, F.EX_VALID_FROM_INDEXNOW_LAST_ACCESS));
        return new RawSearchUrlSample(
                chRow.getString(F.PATH.name),
                chRow.getString(F.TITLE.name),
                getNullableDate(chRow, F.LAST_ACCESS.getName()),
                statusInfo
        );
    };

    public enum F {
        HOST_ID("host_id"),
        PATH("path"),
        LAST_ACCESS("last_access"),
        TITLE("title"),
        STATUS("status"),
        EX_REGION("ex_region"),
        EX_ADD_TIME("ex_add_time"),
        EX_BEAUTY_URL("ex_beauty_url"),
        EX_HTTP_CODE("ex_http_code"),
        EX_MAIN_HOST("ex_main_host"),
        EX_MAIN_PATH("ex_main_path"),
        EX_MAIN_REGION("ex_main_region"),
        EX_REDIR_TARGET("ex_redir_target"),
        EX_REL_CANONICAL_TARGET("ex_rel_canonical_target"),
        EX_IS_FAKE("ex_is_fake"),
        EX_IS_INDEXED("ex_is_indexed"),
        EX_IS_TURBO("ex_is_turbo"),
        EX_IS_FROM_SITEMAP("ex_is_from_sitemap"),
        EX_IS_SEARCHABLE("ex_is_searchable"),
        EX_TURBO_SOURCE_FLAGS("ex_turbo_source_flags"),
        REL_CANONICAL_TARGET("rel_canonical_target"),
        EX_VALID_FROM_METRIKA_LAST_ACCESS("ex_valid_from_metrika_last_access"),
        EX_VALID_FROM_INDEXNOW_LAST_ACCESS("ex_valid_from_indexnow_last_access")
        ;

        private final String name;

        F(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }
}
