package ru.yandex.webmaster3.storage.importanturls;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

import com.datastax.driver.core.utils.UUIDs;
import lombok.Setter;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.yandex.webmaster3.storage.clickhouse.TableType;
import ru.yandex.webmaster3.storage.clickhouse.dao.ClickhouseTablesRepository;
import ru.yandex.webmaster3.storage.clickhouse.replication.MdbClickhouseReplicationManager;
import ru.yandex.webmaster3.storage.clickhouse.replication.data.ClickhouseReplicationCommand;
import ru.yandex.webmaster3.storage.clickhouse.replication.data.ClickhouseReplicationPriority;
import ru.yandex.webmaster3.storage.clickhouse.system.dao.ClickhouseSystemTablesCHDao;
import ru.yandex.webmaster3.storage.util.clickhouse2.AbstractClickhouseDao;
import ru.yandex.webmaster3.storage.util.clickhouse2.CHPrimitiveType;
import ru.yandex.webmaster3.storage.util.clickhouse2.CHTable;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseException;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseHost;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseQueryContext;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseServer;
import ru.yandex.webmaster3.storage.util.yt.YtException;
import ru.yandex.webmaster3.storage.util.yt.YtNodeAttributes;
import ru.yandex.webmaster3.storage.util.yt.YtOperationId;
import ru.yandex.webmaster3.storage.util.yt.YtPath;
import ru.yandex.webmaster3.storage.util.yt.YtService;
import ru.yandex.webmaster3.storage.util.yt.YtUtils;
import ru.yandex.webmaster3.storage.ytimport.ImportPriority;
import ru.yandex.webmaster3.storage.ytimport.MdbYtClickhouseImportManager;
import ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoad;
import ru.yandex.webmaster3.storage.ytimport.YtClickhouseImportCommand;
import ru.yandex.webmaster3.storage.ytimport.YtClickhouseTableRelation;

/**
 * @author avhaliullin
 */
public class RecommendedUrlsImportService {

    private static final Logger log = LoggerFactory.getLogger(RecommendedUrlsImportService.class);

    public static final DateTimeFormatter IN_TABLE_NAME_DATE_FORMATTER = DateTimeFormat.forPattern("yyyyMMdd");
    private static final DateTimeFormatter CH_INSERTION_DATE_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd");
    private static final int ROWS_COUNT = 256;
    private static final String TASK_NAME = "RECOMMENDED_URLS";
    private static final String TABLE_NAME = "recommended_urls";

    @Setter
    private YtService ytService;
    @Setter
    private ClickhouseServer clickhouseServer;
    @Setter
    private MdbYtClickhouseImportManager ytClickhouseImportManager;
    @Setter
    private MdbClickhouseReplicationManager clickhouseReplicationManager;
    @Setter
    private ClickhouseTablesRepository clickhouseTablesCDao;
    @Setter
    private ClickhouseSystemTablesCHDao clickhouseSystemTablesCHDao;
    @Setter
    private YtPath mrBinary;
    @Setter
    private YtPath workDir;
    @Setter
    private String database = AbstractClickhouseDao.DB_WEBMASTER3;

    private CHTable chTable;

    public void init() {
        chTable = CHTable.builder()
                .database(database)
                .name("tmp_recommended_urls_%s")
                .partitionBy("toYYYYMM(date)")
            .keyField("date", CHPrimitiveType.Date)
                .keyField("host_id", CHPrimitiveType.String)
                .field("path", CHPrimitiveType.String)
                .field("clicks", CHPrimitiveType.Int64)
                .field("shows", CHPrimitiveType.Int64)
                .build();
    }

    public CHTable getTable() {
        return chTable;
    }

    public YtClickhouseDataLoad prepareRecommendedUrls(YtClickhouseDataLoad importData) {

        LocalDate date = importData.getDateTo();
        List<YtPath> tables = recommendedUrlPreparedTables(importData);

        try {
            ytService.inTransaction(mrBinary).execute(cypressService -> {

                YtUtils.recreateTables(cypressService, tables, new YtNodeAttributes().setCompressionCodec("none"));
                YtOperationId operationId = cypressService.mapReduce(
                        YtUtils.newPrepareTablesForImportBuilder()
                                .addInputTable(importData.getSourceTable())
                                .setOutputTables(tables)
                                .setBinary(mrBinary)
                                .setTask(TASK_NAME)
                                .setLines(ROWS_COUNT)
                                .addMapperArg("--date", date.toString(CH_INSERTION_DATE_FORMATTER))
                                .build());

                if (!cypressService.waitFor(operationId)) {
                    throw new YtException("Prepare recommended urls failed. See " + operationId);
                }
                return true;
            });
        } catch (YtException e) {
            throw new RuntimeException(e);
        }
        return importData.withPreparedTables(tables);
    }

    public YtClickhouseDataLoad importRecommendedUrls(YtClickhouseDataLoad importData) {
        LocalDate date = importData.getDateTo();
        String dateString = IN_TABLE_NAME_DATE_FORMATTER.print(date);
        List<YtPath> tables = recommendedUrlPreparedTables(importData);
        UUID taskId = UUIDs.timeBased();
        log.info("Recommended utls import " + taskId);
        List<YtClickhouseTableRelation> tablesRels = new ArrayList<>();
        int idx = 0;
        int shards = clickhouseServer.getShardsCount();
        for (int shard = 0; shard < shards; shard++) {
            tablesRels.add(new YtClickhouseTableRelation(
                    tables.get(idx++),
                    shard,
                    getTable().replicatedMergeTreeTableName(0, dateString),
                    getTable().createReplicatedMergeTreeSpec(0, dateString)
            ));
        }
        YtClickhouseImportCommand command = new YtClickhouseImportCommand(
                taskId,
                tablesRels,
                getTable().getDatabase(),
                getTable().importSpec(),
                ImportPriority.OFFLINE
        );
        ytClickhouseImportManager.startImport(command);
        return importData.withImportTaskIds(taskId);
    }

    public YtClickhouseDataLoad replicateRecommendedUrls(YtClickhouseDataLoad importData) {
        LocalDate date = importData.getDateTo();
        String dateString = IN_TABLE_NAME_DATE_FORMATTER.print(date);
        UUID taskId = UUIDs.timeBased();
        log.info("Replication id " + taskId);
        List<ClickhouseReplicationCommand.TableInfo> tables = new ArrayList<>();
        int shards = clickhouseServer.getShardsCount();
        for (int shard = 0; shard < shards; shard++) {
            tables.add(new ClickhouseReplicationCommand.TableInfo(
                    getTable().replicatedMergeTreeTableName(0, dateString),
                    getTable().createReplicatedMergeTreeSpec(0, dateString),
                    shard
            ));
        }
        ClickhouseReplicationCommand command = new ClickhouseReplicationCommand(
                taskId,
                getTable().getDatabase(),
                ClickhouseReplicationPriority.OFFLINE,
                tables
        );
        clickhouseReplicationManager.enqueueReplication(command);
        // добавим запись о новых таблицах в Cassandra
        clickhouseTablesCDao.update(getTable().toClickhouseTableInfo(TableType.RECOMMENDED_URLS, shards, dateString));
        return importData.withReplicationTaskIds(taskId);
    }

    public YtClickhouseDataLoad renameRecommendedUrls(YtClickhouseDataLoad importData) {
        String renameQPattern = "RENAME TABLE %1$s.%2$s TO %1$s.%3$s, %1$s.%4$s TO %1$s.%2$s";
        LocalDate date = importData.getDateTo();
        String dateString = IN_TABLE_NAME_DATE_FORMATTER.print(date);
        String database = getTable().getDatabase();
        String targetName = TABLE_NAME;
        String curTableNewName = "old_" + TABLE_NAME;
        String newTableCurName = getTable().replicatedMergeTreeTableName(0, dateString);
        String renameQ = String.format(renameQPattern, database, targetName, curTableNewName, newTableCurName);
        List<ClickhouseHost> hosts = clickhouseServer.getHosts();
        if (hosts.stream().anyMatch(ClickhouseHost::isDown)) {
            throw new RuntimeException("Some clickhouse hosts are down");
        }
        for (ClickhouseHost host : hosts) {
            if (clickhouseSystemTablesCHDao.getTable(host, database, newTableCurName).isEmpty()) {
                // заимпорченной таблицы нет
                if (clickhouseSystemTablesCHDao.getTable(host, database, curTableNewName).isEmpty()) {
                    // и старой переименованной нет - какая-то хрень
                    throw new RuntimeException("Fatal: cannot find imported table " + newTableCurName + " neither renamed old table " + curTableNewName);
                }
                // значит уже переименовали
            } else {
                clickhouseServer.execute(ClickhouseQueryContext.useDefaults().setHost(host), ClickhouseServer.QueryType.INSERT, renameQ, Optional.empty(), Optional.empty());
            }
        }
        return importData.withNextState();
    }

    public YtClickhouseDataLoad cleanRecommendedUrls(YtClickhouseDataLoad importData) {
        String database = getTable().getDatabase();
        String curTableNewName = "old_" + TABLE_NAME;
        List<ClickhouseHost> hosts = clickhouseServer.getHosts();
        try {
            for (ClickhouseHost host : hosts) {
                if (clickhouseSystemTablesCHDao.getTable(host, database, curTableNewName).isPresent()){
                    clickhouseServer.execute(
                            ClickhouseQueryContext.useDefaults().setHost(host),
                            ClickhouseServer.QueryType.INSERT,
                            String.format("DROP TABLE IF EXISTS %1$s.%2$s", database, curTableNewName),
                            Optional.empty(),
                            Optional.empty()
                    );
                }
            }
            ytService.inTransaction(importData.getSourceTable()).execute(cypressService -> {
                for (YtPath preparedTable : recommendedUrlPreparedTables(importData)) {
                    if (cypressService.exists(preparedTable)) {
                        cypressService.remove(preparedTable);
                    }
                }
                return true;
            });
            return importData.withNextState();
        } catch (ClickhouseException | YtException e) {
            throw new RuntimeException(e);
        }
    }

    private List<YtPath> recommendedUrlPreparedTables(YtClickhouseDataLoad importData) {
        List<YtPath> tables = new ArrayList<>();
        int shards = clickhouseServer.getShardsCount();
        final String dateString = importData.getDateTo().toString(IN_TABLE_NAME_DATE_FORMATTER);
        for (int shard = 0; shard < shards; shard++) {
            tables.add(YtPath.path(workDir, dateString + "_shard_" + shard));
        }
        return tables;
    }

}
