package ru.yandex.webmaster3.worker.recommendedquery;

import com.google.common.base.Stopwatch;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.mutable.MutableInt;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.beans.factory.annotation.Autowired;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.core.util.RetryUtils;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskState;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskType;
import ru.yandex.webmaster3.core.worker.task.TaskResult;
import ru.yandex.webmaster3.storage.util.ydb.exception.WebmasterYdbException;
import ru.yandex.webmaster3.storage.recommendedquery.dao.HostsWithOpenedRecommendedYDao;
import ru.yandex.webmaster3.storage.util.yt.*;
import ru.yandex.webmaster3.worker.PeriodicTask;
import ru.yandex.webmaster3.worker.TaskSchedule;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Created by ifilippov5 on 07.11.17.
 */
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class UploadHostsWithOpenedRecommendedPeriodicTask extends PeriodicTask<UploadHostsWithOpenedRecommendedPeriodicTask.TaskState> {
    private static final Logger log = LoggerFactory.getLogger(UploadHostsWithOpenedRecommendedPeriodicTask.class);

    private static final RetryUtils.RetryPolicy RETRY_POLICY = RetryUtils.linearBackoff(10, Duration.standardMinutes(2));

    private static final String TABLE_SCHEMA = "[" +
            "{'name': '" + YtRow.F_HOST_STRING + "', 'type': 'string'}, " +
            "{'name': '" + YtRow.F_OPEN_TIME_STRING + "', 'type': 'int64'}, " +
            "{'name': '" + YtRow.F_OPEN_USER_ID_STRING + "', 'type': 'int64'}]";

    private final HostsWithOpenedRecommendedYDao hostsWithOpenedRecommendedYDao;
    private YtPath workDir;
    private String tableName;
    private YtService ytService;

    @Override
    public Result run(UUID runId) throws Exception {
        setState(new UploadHostsWithOpenedRecommendedPeriodicTask.TaskState());

        Stopwatch stopwatch = Stopwatch.createStarted();
        YtPath tablePath = YtPath.path(workDir, tableName);

        YtTableData table = null;
        try {
             table = ytService.prepareTableData(tablePath.getName(), this::uploadFromCassandra);
             YtTransactionService.TransactionProcess process = new YtUtils.TransactionWriterBuilder(tablePath, table)
                     .withSchema(TABLE_SCHEMA)
                     .withRetry(RETRY_POLICY)
                     .build();

            YtUtils.TransactionExecutor writer = new YtUtils.TransactionExecutor(ytService, workDir);
            writer.execute(process);

        } catch (YtException | IOException e) {
            throw new WebmasterException("YT error",
                    new WebmasterErrorResponse.YTServiceErrorResponse(getClass(), e), e);
        } finally {
            if (table != null) {
                table.delete();
            }
        }
        getState().workTimeMs = stopwatch.elapsed(TimeUnit.MILLISECONDS);

        return new Result(TaskResult.SUCCESS);
    }

    private void uploadFromCassandra(TableWriter tw) {
        MutableInt rowsCount = new MutableInt(0);
        try {
            hostsWithOpenedRecommendedYDao.forEachHost(triple  -> {
                writeToTable(tw,
                        hostToRawString(triple.getLeft()),
                        triple.getMiddle() == null ? null : triple.getMiddle().getMillis(),
                        triple.getRight());
                rowsCount.increment();
            });
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Unable to get from cassandra",
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        }
        getState().rowsProcessed = rowsCount.getValue();
    }

    private void writeToTable(TableWriter tw, String host, Long openTime, Long userId) {
        if (host == null) {
            return;
        }
        tw.column(YtRow.F_HOST_STRING, host);
        tw.columnObject(YtRow.F_OPEN_TIME_STRING, openTime);
        tw.columnObject(YtRow.F_OPEN_USER_ID_STRING, userId);
        try {
            tw.rowEnd();
        } catch (YtException e) {
            throw new RuntimeException(e);
        }
    }

    private static String hostToRawString(WebmasterHostId hostId) {
        if (hostId == null) {
            log.info("Null host detected");
            return null;
        }
        return IdUtils.toHostString(hostId, true, false, false);
    }

    @Required
    public void setYtService(YtService ytService) {
        this.ytService = ytService;
    }

    @Required
    public void setWorkDir(YtPath workDir) {
        this.workDir = workDir;
    }

    @Required
    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    @Override
    public PeriodicTaskType getType() {
        return PeriodicTaskType.UPLOAD_HOSTS_WITH_OPENED_RECOMMENDED;
    }

    @Override
    public TaskSchedule getSchedule() {
        return TaskSchedule.startByCron("0 0 * * * *");
    }

    private static class YtRow {
        static final String F_HOST_STRING = "key";
        static final String F_OPEN_TIME_STRING = "open_time_ms";
        static final String F_OPEN_USER_ID_STRING = "user_id";
    }

    public static class TaskState implements PeriodicTaskState {
        int rowsProcessed;
        long workTimeMs;

        public int getRowsProcessed() {
            return rowsProcessed;
        }

        public long getWorkTimeMs() {
            return workTimeMs;
        }
    }
}
