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

import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;

import ru.yandex.webmaster3.core.util.enums.IntEnum;
import ru.yandex.webmaster3.core.util.enums.IntEnumResolver;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.util.IdUtils;
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.MdbClickhouseServer;
import ru.yandex.webmaster3.storage.util.clickhouse2.SimpleByteArrayOutputStream;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.Format;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.QueryBuilder;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.Statement;

/**
 * ishalaru
 * 17.03.2021
 **/
@Repository
@Slf4j
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
public class RealtimeUrlHttpCodesCHDao extends AbstractClickhouseDao {
    public static final String DATABASE = DB_WEBMASTER3_SEARCHURLS;
    public static final String TABLE_NAME_POSTFIX = "realtime_url_http_codes_distributed";
    private static final String TABLE_NAME = DATABASE + "." + TABLE_NAME_POSTFIX;
    @Qualifier("legacyMdbClickhouseServer")
    private final MdbClickhouseServer clickhouseServer;
    private static final int MINIMUM_REQUESTS_PER_HOUR = 10;


    public void forEachStatistics(long hourInterval, Record.HttpGroup group, double threshold, Consumer<StatisticRecord> consumer) {
        String expr = String.format("sumIf(cnt, http_group=%s) / cnt2", group.value);
        Statement query = QueryBuilder.select(F.HOST_ID,
                "sumIf(cnt, http_group=0) as success_cnt",
                "sumIf(cnt, http_group=1) as internal_error_cnt",
                "sumIf(cnt, http_group=2) as server_error_cnt",
                "sum(cnt) as cnt2"
        ).from(TABLE_NAME)
                .prewhere(QueryBuilder.eq(F.HOUR_INTERVAL, hourInterval))
                .groupBy(F.HOST_ID)
                .having(QueryBuilder.gt(expr, threshold))
                .and(QueryBuilder.gt("cnt2", MINIMUM_REQUESTS_PER_HOUR));

        clickhouseServer.forEach(query.toString(), chRow -> consumer.accept(STATISTICS_MAPPER.apply(chRow)));
    }

    public void addRecord(List<Record> items) {
        Statement st = QueryBuilder.insertInto(DATABASE, TABLE_NAME_POSTFIX)
                .fields(
                        F.DATE,
                        F.HOST_ID,
                        F.HOUR_INTERVAL,
                        F.HTTP_GROUP,
                        F.COUNT
                ).format(Format.Type.TAB_SEPARATED);

        String date = toClickhouseDate(LocalDate.now());
        try(SimpleByteArrayOutputStream bs = new SimpleByteArrayOutputStream()) {

            for (var item : items) {
                packRowValues(bs,
                        date,
                        item.getHostId(),
                        item.getHourInterval(),
                        item.getHttpGroup().value(),
                        item.getCount()
                );
            }
            if (bs.size() == 0L) {
                return;
            }

            ClickhouseQueryContext.Builder context = ClickhouseQueryContext.useDefaults();
            clickhouseServer.insert(context, st.toString(), bs.toInputStream());
        } catch (IOException e) {
            throw new RuntimeException("Error when inserting realtime_url_http_codes records", e);
        }
    }

    private static final Function<CHRow, Record> MAPPER = chRow ->
            new Record(chRow.getHostId(F.HOST_ID), chRow.getLong(F.HOUR_INTERVAL),
                    chRow.getIntEnum(F.HTTP_GROUP, Record.HttpGroup.R), chRow.getLong(F.COUNT));

    private static final Function<CHRow, StatisticRecord> STATISTICS_MAPPER = chRow ->
            new StatisticRecord(
                    chRow.getHostId(F.HOST_ID),
                    chRow.getLongUnsafe("success_cnt"),
                    chRow.getLongUnsafe("internal_error_cnt"),
                    chRow.getLongUnsafe("server_error_cnt")
    );

    @Value
    public static class Record {
        WebmasterHostId hostId;
        long hourInterval;
        HttpGroup httpGroup;
        long count;

        public static Record of(String url, long hourInterval, long httpCode, long count) {


            return new Record(IdUtils.urlToHostId(url), hourInterval, HttpGroup.of(httpCode), count);
        }

        @RequiredArgsConstructor
        public enum  HttpGroup implements IntEnum {
            OK(0, 0),
            INTERNAL_ERROR(1, 4),
            SERVER_ERROR(2, 5)
            ;

            private final int value;
            @Getter
            private final int httpCodeValue;

            public static HttpGroup of(long httpCode) {
                if (httpCode / 100 == SERVER_ERROR.getHttpCodeValue()) {
                    return SERVER_ERROR ;
                } else if (httpCode / 100 == INTERNAL_ERROR.getHttpCodeValue()) {
                    return INTERNAL_ERROR;
                }
                return OK;
            }

            @Override
            public int value() {
                return value;
            }
            public static final IntEnumResolver<HttpGroup> R = IntEnumResolver.r(HttpGroup.class);
        }
    }

    @Value
    public static class StatisticRecord {
        WebmasterHostId hostId;
        long successCnt;
        long internalErrorCnt;
        long serverErrorCnt;
    }

    private interface F {
        String DATE = "date";
        String HOST_ID = "host_id";
        String HOUR_INTERVAL = "hour_interval";
        String HTTP_GROUP = "http_group";
        String COUNT = "cnt";
    }


}
