package ru.yandex.webmaster3.storage.nca;

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

import lombok.extern.slf4j.Slf4j;
import org.joda.time.Duration;

import ru.yandex.webmaster3.core.util.RetryUtils;
import ru.yandex.webmaster3.storage.nca.data.CertificateIdByDomainEntry;
import ru.yandex.webmaster3.storage.util.clickhouse2.AbstractClickhouseDao;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseHost;
import ru.yandex.webmaster3.storage.util.clickhouse2.SimpleByteArrayOutputStream;
import ru.yandex.webmaster3.storage.util.clickhouse2.TempDataChunksStoreUtil;
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;

/**
 * @author kravchenko99
 * @date 5/4/22
 */

@Slf4j
public abstract class AbstractCtlogCertificatesCHDao<T> extends AbstractClickhouseDao {
    private static final int TIME_ALIVE_OF_TEMP_TABLE_IN_MINUTES = 120;
    private static final RetryUtils.RetryPolicy RETRY_POLICY = RetryUtils.expBackoff(3, Duration.standardSeconds(2));


    public void insert(List<CertificateIdByDomainEntry> entries) {
        Map<Integer, List<T>> collect = entries.stream().filter(x -> x.getDomains() != null)
                .flatMap(this::rowFromEntry)
                .collect(Collectors.groupingBy(this::getShard));

        for (var entry : collect.entrySet()) {
            ClickhouseHost host = TempDataChunksStoreUtil.selectHostForShard(entry.getKey(), getClickhouseServer(),
                    TIME_ALIVE_OF_TEMP_TABLE_IN_MINUTES);

            if (host == null) {
                log.error("Failed to select host");
                throw new RuntimeException("Expected one host");
            }
            log.info("Selected host: {}", host);
            try {
                RetryUtils.execute(RETRY_POLICY, () -> getClickhouseServer().executeWithFixedHost(host, () -> {
                            insertData(entry.getValue());
                            return null;
                        }
                ));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            }
        }
    }

    // не используем try-with-resources так как у bs метод close пустой
    @SuppressWarnings("java:S2095")
    protected void insertData(List<T> entries) {
        Statement st = QueryBuilder.insertInto(getDbName(), getTableName())
                .fields(getInsertFields())
                .format(Format.Type.TAB_SEPARATED);

        SimpleByteArrayOutputStream bs = new SimpleByteArrayOutputStream();

        for (var entry : entries) {
            bs = packRow(bs, entry);
        }

        insert(st, bs.toInputStream());
    }

    protected abstract SimpleByteArrayOutputStream packRow(SimpleByteArrayOutputStream bs, T row);
    protected abstract int getShard(T row);
    protected abstract Stream<T> rowFromEntry(CertificateIdByDomainEntry entry);

    public abstract String getDbName();
    public abstract String getTableName();
    public abstract String[] getInsertFields();

    // копия в CtlogCertificatesCHDao
    protected interface F {
        //key
        String REVERSED_DOMAIN = "reversed_domain";
        String NOT_BEFORE = "not_before";
        String SERIES_NUMBER = "series_number";

        // info about ctlog
        String CTLOG = "ctlog";
        String CTLOG_ROW = "ctlog_row";

        //
        String CERTIFICATE = "certificate";
        String TYPE = "type";

    }
}
