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

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.google.common.base.Preconditions;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import org.joda.time.Duration;
import org.joda.time.Instant;

import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.core.util.TimeUtils;
import ru.yandex.webmaster3.storage.searchurl.offline.data.SearchBaseImportInfo;
import ru.yandex.webmaster3.storage.searchurl.samples.data.RawSearchUrlEventSample;
import ru.yandex.webmaster3.storage.searchurl.samples.data.RawSearchUrlEventSampleWithHost;
import ru.yandex.webmaster3.storage.searchurl.samples.data.SearchUrlEventType;
import ru.yandex.webmaster3.storage.util.clickhouse2.AbstractClickhouseDao;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseException;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseQueryContext;
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.Statement;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.Where;

import static ru.yandex.webmaster3.storage.searchurl.samples.dao.SearchUrlEventSamplesCHDao.F;

/**
 * @author aherman
 */
public class SearchUrlEmailSamplesCHDao extends AbstractClickhouseDao {

    public String createTableName(Instant searchBaseDate) {
        return "search_url_email_samples_" + searchBaseDate.toString(TimeUtils.DF_YYYYMMDD_MSK);
    }

    public void createTable(SearchBaseImportInfo table) {
        String q = getCreateStatement(table, true);
        insert(q);
    }

    public static String getCreateStatement(SearchBaseImportInfo table, boolean addCreateTable) {
        return String.format(
                (addCreateTable ? "CREATE TABLE %1$s.%2$s" : "") + " ("
                        + "  host_id String"
                        + ", base_date Int64"
                        + ", last_access Int64"
                        + ", event_type Int8"
                        + ", path String"
                        + ") "
                        + "ENGINE = ReplicatedMergeTree('/webmaster3/clickhouse/tables/%1$s/%2$s', '{replica}') ORDER BY (host_id, base_date, last_access)",
                table.getDbName(), table.getReadTableName()
        );
    }

    public void dropAllTables(SearchBaseImportInfo table) {
        String q = String.format("DROP TABLE IF EXISTS %1$s.%2$s", table.getDbName(), table.getReadTableName());
        executeOnAllHosts(q);
    }

    public void insertEvents(SearchBaseImportInfo eventsTable, SearchBaseImportInfo emailSamplesTable, Range<WebmasterHostId> hostRange) {
        String condition = String.format("base_date = %d",
                TimeUtils.toUnixTimestamp(eventsTable.getSearchBaseDate()));

        if (hostRange.hasLowerBound()) {
            Preconditions.checkArgument(hostRange.lowerBoundType() == BoundType.CLOSED, "Unsupported lower bound: " + hostRange);
            condition += String.format(" AND host_id >= '%s'", hostRange.lowerEndpoint().toStringId());
        }
        if (hostRange.hasUpperBound()) {
            Preconditions.checkArgument(hostRange.upperBoundType() == BoundType.OPEN, "Unsupported upper bound: " + hostRange);
            condition += String.format(" AND host_id < '%s'", hostRange.upperEndpoint().toStringId());
        }

        String q = String.format("INSERT INTO %1$s.%2$s SELECT host_id, base_date, last_access, event_type, path"
                        + " FROM %3$s.%4$s PREWHERE %5$s ORDER BY host_id, event_type, last_access DESC limit 10 by host_id, event_type",
                emailSamplesTable.getDbName(), emailSamplesTable.getReadTableName(),
                eventsTable.getDbName(), eventsTable.getReadTableName(),
                condition
        );

        ClickhouseQueryContext.Builder chContext = ClickhouseQueryContext.useDefaults()
                .setTimeout(Duration.standardMinutes(10));

        getClickhouseServer().insert(chContext, q);
    }

    public Map<WebmasterHostId, List<RawSearchUrlEventSample>> getSamples(SearchBaseImportInfo table, Collection<WebmasterHostId> hosts) throws ClickhouseException {
        Statement st = QueryBuilder.select(
                F.HOST_ID.getName(),
                F.SEARCH_BASE_DATE.getName(),
                F.LAST_ACCESS.getName(),
                F.EVENT_TYPE.getName(),
                F.PATH.getName()
        ).from(table.getDbName() + "." + table.getReadTableName())
                .where(QueryBuilder.in(F.HOST_ID.getName(), hosts));
        return collectAll(st, Collectors.groupingBy(
                chRow -> IdUtils.stringToHostId(chRow.getString(F.HOST_ID.getName())),
                Collectors.mapping(
                        chRow -> new RawSearchUrlEventSample(
                                chRow.getString(F.PATH.getName()),
                                null,
                                getNullableDate(chRow, F.LAST_ACCESS.getName()),
                                getNullableDate(chRow, F.SEARCH_BASE_DATE.getName()),
                                SearchUrlEventType.R.fromValue(chRow.getInt(F.EVENT_TYPE.getName())),
                                null
                        ), Collectors.toList())
        ));
    }

    public List<RawSearchUrlEventSampleWithHost> getSamples(SearchBaseImportInfo emailSamplesTable, Range<WebmasterHostId> hostRange) {
        Where where = QueryBuilder.selectDistinct(
                F.HOST_ID.getName(),
                F.SEARCH_BASE_DATE.getName(),
                F.LAST_ACCESS.getName(),
                F.EVENT_TYPE.getName(),
                F.PATH.getName())
                .from(emailSamplesTable.getDbName() + "." + emailSamplesTable.getReadTableName())
                .where(QueryBuilder
                        .gte(F.HOST_ID.getName(), hostRange.lowerEndpoint().toStringId()));
        if (hostRange.hasUpperBound()) {
            if (hostRange.upperBoundType() == BoundType.OPEN) {
                where = where.and(QueryBuilder.lt(F.HOST_ID.getName(), hostRange.upperEndpoint().toStringId()));
            } else {
                where = where.and(QueryBuilder.lte(F.HOST_ID.getName(), hostRange.upperEndpoint().toStringId()));
            }
        }
        GroupableLimitableOrderable fst = where.orderBy(F.HOST_ID.getName(), OrderBy.Direction.ASC);
        return queryAll(fst, chRow -> {
            WebmasterHostId hostId =
                    IdUtils.stringToHostId(chRow.getString(F.HOST_ID.getName()));
            return new RawSearchUrlEventSampleWithHost(
                    chRow.getString(F.PATH.getName()),
                    null,
                    getNullableDate(chRow, F.LAST_ACCESS.getName()),
                    getNullableDate(chRow, F.SEARCH_BASE_DATE.getName()),
                    SearchUrlEventType.R.fromValue(chRow.getInt(F.EVENT_TYPE.getName())),
                    null,
                    hostId);
        });
    }
}
