package ru.yandex.webmaster3.storage.links2.dao;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.Instant;
import ru.yandex.webmaster3.core.codes.LinkType;
import ru.yandex.webmaster3.core.data.HttpCodeInfo;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.link.HostLinkSample;
import ru.yandex.webmaster3.core.links2.HostLinkSourceSample;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.storage.clickhouse.table.ExternalLinkSamplesTable;
import ru.yandex.webmaster3.storage.links2.ExternalLinksGroupedOrder;
import ru.yandex.webmaster3.storage.links2.ExternalLinksOrder;
import ru.yandex.webmaster3.storage.util.clickhouse2.AbstractClickhouseDao;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseException;
import ru.yandex.webmaster3.storage.util.clickhouse2.condition.Condition;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.From;
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 ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.Case;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author tsyplyaev
 */
public class ExternalLinks2SamplesCHDao extends AbstractClickhouseDao {
    public Long countSamples(ExternalLinkSamplesTable table, WebmasterHostId hostId, Condition parameterCondition,
            boolean filterBroken) throws ClickhouseException
    {
        Where st = QueryBuilder.select().countAll()
                .from(table.getTableName(getClickhouseServer(), hostId))
                .where(QueryBuilder.eq(F.HOST_ID.getName(), hostId));

        st = filter(st, parameterCondition);

        if (filterBroken) {
            st = st.and(QueryBuilder.notIn(F.HOST_HTTP_CODE.getName(), LinkType.NORMAL.getCodes()));
            st = st.and(QueryBuilder.notIn(F.HOST_HTTP_CODE.getName(), LinkType.NOT_DOWNLOADED.getCodes()));
            st = st.and(QueryBuilder.notIn(F.HOST_HTTP_CODE.getName(), LinkType.REDIRECT.getCodes()));
        }

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

    public List<HostLinkSample> listSamples(ExternalLinkSamplesTable table, WebmasterHostId hostId, long limitFrom,
            long limitSize,
            Condition parameterCondition,
            boolean filterBroken,
            ExternalLinksOrder orderBy, OrderBy.Direction orderDirection, boolean deleted)
            throws ClickhouseException
    {

        Where st = QueryBuilder.select()
                .from(table.getTableName(getClickhouseServer(), hostId))
                .where(QueryBuilder.eq(F.HOST_ID.getName(), hostId));
        st = filter(st, parameterCondition);

        if (filterBroken) {
            st = st.and(QueryBuilder.notIn(F.HOST_HTTP_CODE.getName(), LinkType.getNotBrokenCodes()));
        }

        GroupableLimitableOrderable fst = st;
        fst = getOrder(fst, orderBy, orderDirection);

        fst = fst.limit((int) limitFrom, (int) limitSize);

        return getClickhouseServer().queryAll(table.chContext(getClickhouseServer(), hostId), fst.toString(), r -> {
            String hostPath = r.getString(F.HOST_PATH.getName());
            int hostHttpCode = r.getInt(F.HOST_HTTP_CODE.getName());
            int hostLastAccess = r.getInt(F.HOST_LAST_ACCESS.getName());

            WebmasterHostId sourceHostId = IdUtils.stringToHostId(r.getString(F.SOURCE_HOST_ID.getName()));
            String sourcePath = r.getString(F.SOURCE_PATH.getName());
            int sourceLastAccess = r.getInt(F.SOURCE_LAST_ACCESS.getName());
            int sourceHostIks = r.getInt(F.SOURCE_IKS.getName());

            String text = r.getString(F.LINK_TEXT.getName());
            int linkDate = r.getInt(F.LINK_DATE.getName());

            Instant deletedDate = null;
            if (deleted) {
                deletedDate = toDate((int)r.getLongUnsafe(F.DELETED_DATE.getName()));
            }

            String hostUrl = IdUtils.hostIdToUrl(hostId) + (hostPath != null ? hostPath : "");
            String sourceUrl = IdUtils.hostIdToUrl(sourceHostId) + (sourcePath != null ? sourcePath : "");

            return new HostLinkSample(hostId, hostUrl, hostPath, toDate(hostLastAccess), hostHttpCode,
                    new HttpCodeInfo(hostHttpCode), sourceHostId, sourcePath, sourceUrl,
                    toDate(sourceLastAccess), 200, new HttpCodeInfo(200), sourceHostIks, toDate(linkDate), text, deletedDate);
        });
    }

    private GroupableLimitableOrderable getOrder(GroupableLimitableOrderable fst, ExternalLinksOrder orderBy,
            OrderBy.Direction orderDirection)
    {
        orderBy = ObjectUtils.defaultIfNull(orderBy, ExternalLinksOrder.HOST_LAST_ACCESS);
        orderDirection = ObjectUtils.defaultIfNull(orderDirection, OrderBy.Direction.ASC);

        List<Pair<Object, OrderBy.Direction>> orderFields = new ArrayList<>();

        switch (orderBy) {
            case HOST_HTTP_CODE:
                orderFields.add(Pair.of(F.HOST_HTTP_CODE.getName(), orderDirection));
                orderFields.add(Pair.of(F.HOST_PATH.getName(), OrderBy.Direction.ASC));
                orderFields.add(Pair.of(F.SOURCE_PATH.getName(), OrderBy.Direction.ASC));
                break;
            case HOST_LAST_ACCESS:
                orderFields.add(Pair.of(F.HOST_LAST_ACCESS.getName(), orderDirection));
                orderFields.add(Pair.of(F.HOST_PATH.getName(), OrderBy.Direction.ASC));
                orderFields.add(Pair.of(F.SOURCE_PATH.getName(), OrderBy.Direction.ASC));
                break;
            case HOST_PATH:
                orderFields.add(Pair.of(F.HOST_PATH.getName(), orderDirection));
                orderFields.add(Pair.of(F.SOURCE_PATH.getName(), OrderBy.Direction.ASC));
                break;
            case LINK_DATE:
                orderFields.add(Pair.of(F.LINK_DATE.getName(), orderDirection));
                orderFields.add(Pair.of(F.HOST_PATH.getName(), OrderBy.Direction.ASC));
                orderFields.add(Pair.of(F.SOURCE_PATH.getName(), OrderBy.Direction.ASC));
                break;
            case LINK_TEXT:
                orderFields.add(Pair.of(F.LINK_TEXT.getName(), orderDirection));
                orderFields.add(Pair.of(F.HOST_PATH.getName(), OrderBy.Direction.ASC));
                orderFields.add(Pair.of(F.SOURCE_PATH.getName(), OrderBy.Direction.ASC));
                break;
            case SOURCE_HOST_ID:
                orderFields.add(Pair.of(F.SOURCE_HOST_ID.getName(), orderDirection));
                orderFields.add(Pair.of(F.HOST_PATH.getName(), OrderBy.Direction.ASC));
                orderFields.add(Pair.of(F.SOURCE_PATH.getName(), OrderBy.Direction.ASC));
                break;
            case SOURCE_LAST_ACCESS:
                orderFields.add(Pair.of(F.SOURCE_LAST_ACCESS.getName(), orderDirection));
                orderFields.add(Pair.of(F.HOST_PATH.getName(), OrderBy.Direction.ASC));
                orderFields.add(Pair.of(F.SOURCE_PATH.getName(), OrderBy.Direction.ASC));
                break;
            case SOURCE_PATH:
                orderFields.add(Pair.of(F.SOURCE_PATH.getName(), orderDirection));
                orderFields.add(Pair.of(F.HOST_PATH.getName(), OrderBy.Direction.ASC));
                break;
            case SOURCE_SQI:
                orderFields.add(Pair.of(F.SOURCE_IKS.getName(), orderDirection));
                orderFields.add(Pair.of(F.HOST_PATH.getName(), OrderBy.Direction.ASC));
                orderFields.add(Pair.of(F.SOURCE_PATH.getName(), OrderBy.Direction.ASC));
                break;
            default:
                orderFields.add(Pair.of(F.HOST_LAST_ACCESS.getName(), orderDirection));
                orderFields.add(Pair.of(F.HOST_PATH.getName(), OrderBy.Direction.ASC));
                orderFields.add(Pair.of(F.SOURCE_PATH.getName(), OrderBy.Direction.ASC));
        }

        return fst.orderBy(orderFields);
    }

    public Long countSourceSamples(ExternalLinkSamplesTable table, WebmasterHostId hostId,
            Condition condition,
            boolean filterBroken) throws ClickhouseException
    {
        //inner request
        Where whereInner = QueryBuilder.select(F.SOURCE_HOST_ID.getName())
                .from(table.getTableName(getClickhouseServer(), hostId))
                .where(QueryBuilder.eq(F.HOST_ID.getName(), hostId));

        if (filterBroken) {
            whereInner = whereInner.and(QueryBuilder.notIn(F.HOST_HTTP_CODE.getName(), LinkType.NORMAL.getCodes()));
            whereInner = whereInner.and(QueryBuilder.notIn(F.HOST_HTTP_CODE.getName(), LinkType.NOT_DOWNLOADED.getCodes()));
            whereInner = whereInner.and(QueryBuilder.notIn(F.HOST_HTTP_CODE.getName(), LinkType.REDIRECT.getCodes()));
        }
        whereInner = filter(whereInner, condition);

        Statement innerSelect = whereInner.groupBy(F.SOURCE_HOST_ID.getName());

        //outer request
        Statement st = QueryBuilder.select()
                .countAll()
                .from(innerSelect);

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

    public List<HostLinkSourceSample> listSourceSamples(ExternalLinkSamplesTable table, WebmasterHostId hostId,
            long limitFrom, long limitSize,
            Condition condition,
            boolean filterBroken,
            ExternalLinksGroupedOrder orderBy, OrderBy.Direction orderDirection, boolean deleted)
            throws ClickhouseException
    {
        //inner request
        List<String> fields = new ArrayList<>(Arrays.asList(
                F.SOURCE_HOST_ID.getName(),
                QueryBuilder.as(QueryBuilder.count(), F.LINK_COUNT_GP.getName()).toString(),
                QueryBuilder.as(QueryBuilder.min(F.LINK_DATE.getName()), F.FIRST_LINK_DATE_GP.getName()).toString(),
                QueryBuilder.as(QueryBuilder.max(F.LINK_DATE.getName()), F.LAST_LINK_DATE_GP.getName()).toString(),
                QueryBuilder.as(QueryBuilder.any(F.SOURCE_IKS.getName()), F.SOURCE_IKS_GP.getName()).toString()
        ));

        if (deleted) {
            fields.add(QueryBuilder.as(QueryBuilder.max(F.DELETED_DATE.getName()), F.DELETED_DATE_GP.getName()).toString());
        }

        Where where = QueryBuilder
                .select(fields.toArray(new String[fields.size()]))
                .from(table.getTableName(getClickhouseServer(), hostId))
                .where(QueryBuilder.eq(F.HOST_ID.getName(), hostId));

        if (filterBroken) {
            where = where.and(QueryBuilder.notIn(F.HOST_HTTP_CODE.getName(), LinkType.NORMAL.getCodes()));
            where = where.and(QueryBuilder.notIn(F.HOST_HTTP_CODE.getName(), LinkType.NOT_DOWNLOADED.getCodes()));
            where = where.and(QueryBuilder.notIn(F.HOST_HTTP_CODE.getName(), LinkType.REDIRECT.getCodes()));
        }
        where = filter(where, condition);

        Statement innerSelect = where.groupBy(F.SOURCE_HOST_ID.getName());

        //outer request
        From from = QueryBuilder.select().from(innerSelect);

        //sort
        GroupableLimitableOrderable st = getGroupedOrder(from, orderBy, orderDirection);
        st = st.limit((int) limitFrom, (int) limitSize);

        return getClickhouseServer().queryAll(table.chContext(getClickhouseServer(), hostId), st.toString(), r -> {
            Instant deletedDate = null;
            if (deleted) {
                deletedDate = toDate(r.getInt(F.DELETED_DATE_GP.getName()));
            }

            return new HostLinkSourceSample(
                    IdUtils.stringToHostId(r.getString(F.SOURCE_HOST_ID.getName())),
                    (int)r.getLongUnsafe(F.LINK_COUNT_GP.getName()),
                    (int)r.getLongUnsafe(F.SOURCE_IKS_GP.getName()),
                    toDate(r.getInt(F.FIRST_LINK_DATE_GP.getName())),
                    toDate(r.getInt(F.LAST_LINK_DATE_GP.getName())),
                    deletedDate);
        });
    }

    private GroupableLimitableOrderable getGroupedOrder(GroupableLimitableOrderable st,
            ExternalLinksGroupedOrder orderBy,
            OrderBy.Direction orderDirection)
    {
        orderBy = ObjectUtils.defaultIfNull(orderBy, ExternalLinksGroupedOrder.SOURCE_HOST_ID);
        orderDirection = ObjectUtils.defaultIfNull(orderDirection, OrderBy.Direction.ASC);

        List<Pair<Object, OrderBy.Direction>> orderFields = new ArrayList<>();

        switch (orderBy) {
            case FIRST_LINK_DATE:
                orderFields.add(Pair.of(F.FIRST_LINK_DATE_GP.getName(), orderDirection));
                orderFields.add(Pair.of(F.SOURCE_HOST_ID.getName(), OrderBy.Direction.ASC));
                break;
            case LAST_LINK_DATE:
                orderFields.add(Pair.of(F.LAST_LINK_DATE_GP.getName(), orderDirection));
                orderFields.add(Pair.of(F.SOURCE_HOST_ID.getName(), OrderBy.Direction.ASC));
                break;
            case LINKS_COUNT:
                orderFields.add(Pair.of(F.LINK_COUNT_GP.getName(), orderDirection));
                orderFields.add(Pair.of(F.SOURCE_HOST_ID.getName(), OrderBy.Direction.ASC));
                break;
            case SOURCE_HOST_ID:
                orderFields.add(Pair.of(F.SOURCE_HOST_ID.getName(), orderDirection));
                break;
            case SOURCE_SQI:
                orderFields.add(Pair.of(F.SOURCE_IKS_GP.getName(), orderDirection));
                orderFields.add(Pair.of(F.SOURCE_HOST_ID.getName(), OrderBy.Direction.ASC));
                break;
            default:
                orderFields.add(Pair.of(F.LAST_LINK_DATE_GP.getName(), orderDirection));
                orderFields.add(Pair.of(F.SOURCE_HOST_ID.getName(), OrderBy.Direction.ASC));
                break;
        }

        return st.orderBy(orderFields);
    }


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

    public enum F {
        HOST_ID("host_id"),

        HOST_PATH("host_path"),
        HOST_URL_UTF("host_url_utf"),
        HOST_HTTP_CODE("host_http_code"),
        HOST_LAST_ACCESS("host_last_access"),

        SOURCE_HOST_ID("source_host_id"),
        SOURCE_PATH("source_path"),
        SOURCE_URL_UTF("source_url_utf"),
        SOURCE_LAST_ACCESS("source_last_access"),
        SOURCE_IKS("source_iks"),

        LINK_TEXT("link_text"),
        LINK_DATE("link_date"),

        DELETED_DATE("link_delete_date"),

        FIRST_LINK_DATE_GP("FIRST_LINK_DATE_GP"),
        LAST_LINK_DATE_GP("LAST_LINK_DATE_GP"),
        LINK_COUNT_GP("LINK_COUNT_GP"),
        SOURCE_IKS_GP("SOURCE_IKS_GP"),
        DELETED_DATE_GP("LINK_DELETE_DATE_GP"),
        ;

        private final String name;

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

        public String getName() {
            return name;
        }
    }

    private static Instant toDate(int unixSeconds) {
        return new Instant(TimeUnit.SECONDS.toMillis(unixSeconds));
    }
}
