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

import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;

import ru.yandex.webmaster3.core.data.HttpCodeInfo;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.util.FNVHash;
import ru.yandex.webmaster3.storage.indexing2.samples.data.IndexedUrlEventSample;
import ru.yandex.webmaster3.storage.indexing2.samples.data.IndexingEventsAvailableCodes;
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.condition.Condition;
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 ru.yandex.wmtools.common.util.http.YandexHttpStatus;

/**
 * @author avhaliullin
 */
public class IndexingEventSamplesCHDao extends AbstractClickhouseDao {
    private static final String TABLE_MERGED = "indexing_event_samples_merged_";
    public static final int PART_COUNT = 4;

    public IndexingEventsAvailableCodes getDistinctCodes(WebmasterHostId hostId, Condition filters) {
        String prevCodes = "prev_codes";
        String curCodes = "curCodes";
        Where where = QueryBuilder.select(
                "groupUniqArray(" + F.PREV_HTTP_CODE + ") AS " + prevCodes,
                "groupUniqArray(" + F.CUR_HTTP_CODE + ") AS " + curCodes
        ).from(DB_WEBMASTER3_INDEXING, getTable(hostId))
                .where(QueryBuilder.eq(F.HOST_ID, hostId.toString()));
        where = filter(where, filters);
        return getClickhouseServer().queryOne(chContext(hostId), where, row -> new IndexingEventsAvailableCodes(
                extractHttpCodes(row, prevCodes),
                extractHttpCodes(row, curCodes)
        )).orElse(IndexingEventsAvailableCodes.EMPTY);
    }

    public long getSamplesCount(WebmasterHostId hostId, Condition filters) {
        Where where = QueryBuilder.select().countAll()
                .from(DB_WEBMASTER3_INDEXING, getTable(hostId))
                .where(QueryBuilder.eq(F.HOST_ID, hostId.toString()));
        where = filter(where, filters);
        return getClickhouseServer().queryOne(chContext(hostId), where, row -> row.getLongUnsafe(0)).orElse(0L);
    }

    public List<IndexedUrlEventSample> getSamples(WebmasterHostId hostId, Condition filters,
                                                  IndexingEventsOrderField orderBy, OrderBy.Direction orderDirection,
                                                  int limitFrom, int limitSize) {
        Where where = QueryBuilder.select(F.LAST_ACCESS, F.PATH, F.PREV_HTTP_CODE, F.CUR_HTTP_CODE)
                .from(DB_WEBMASTER3_INDEXING, getTable(hostId))
                .where(QueryBuilder.eq(F.HOST_ID, hostId.toString()));
        where = filter(where, filters);
        return getClickhouseServer().queryAll(chContext(hostId),
                where.orderBy(Arrays.asList(
                        Pair.of(getOrderByField(orderBy), orderDirection),
                        Pair.of(F.PATH, OrderBy.Direction.ASC)
                ))
                        .limit(limitFrom, limitSize),
                row -> new IndexedUrlEventSample(
                        row.getString(F.PATH),
                        HttpCodeInfo.fromHttpStatus(parseStatus(row.getInt(F.PREV_HTTP_CODE))),
                        HttpCodeInfo.fromHttpStatus(parseStatus(row.getInt(F.CUR_HTTP_CODE))),
                        new DateTime(row.getLong(F.LAST_ACCESS))
                )
        );
    }

    private static String getOrderByField(IndexingEventsOrderField orderField) {
        switch (orderField) {
            case LAST_ACCESS:
                return F.LAST_ACCESS;
            default:
                throw new RuntimeException("Unknown order field " + orderField);
        }
    }

    private static YandexHttpStatus parseStatus(int status) {
        if (status == -1) {
            // На сегодняшний момент (30.03.2017) для страниц ни разу не обойденных роботом
            // в базе хранится http код -1, что соответствует YandexHttpStatus.UNKNOWN.
            // Это не то, что мы хотим возвратить, поэтому мы обрабатываем его здесь специальным
            // образом. В будущем хочется хранить в базе не -1, а YandexHttpStatus.NEVER_DOWNLOADED,
            // что сделает этот костыль не нужным.
            return YandexHttpStatus.NEVER_DOWNLOADED;
        }
        return YandexHttpStatus.parseCode(status);
    }

    private static Set<YandexHttpStatus> extractHttpCodes(CHRow row, String field) {
        return row.getIntListUnsafe(field)
                .stream()
                .map(IndexingEventSamplesCHDao::parseStatus)
                .collect(Collectors.toCollection(() -> EnumSet.noneOf(YandexHttpStatus.class)));
    }

    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;
            }
        });
    }

    private ClickhouseQueryContext.Builder chContext(WebmasterHostId hostId) {
        int shardsCount = getClickhouseServer().getShardsCount();
        int shard = (int) FNVHash.hash64Mod(hostId.toString(), shardsCount * PART_COUNT) / PART_COUNT;
        return withShard(shard);
    }

    private String getTable(WebmasterHostId hostId) {
        int shardsCount = getClickhouseServer().getShardsCount();
        int part = (int) FNVHash.hash64Mod(hostId.toString(), shardsCount * PART_COUNT) % PART_COUNT;
        return TABLE_MERGED + part;
    }

    static class F {
        static final String HOST_ID = "host_id";
        static final String LAST_ACCESS = "last_access";
        static final String PATH = "path";
        static final String PREV_HTTP_CODE = "prev_http_code";
        static final String CUR_HTTP_CODE = "cur_http_code";
    }
}
