package ru.yandex.webmaster3.worker.turbo;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.Range;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.format.ISODateTimeFormat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskState;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskType;
import ru.yandex.webmaster3.storage.host.CommonDataState;
import ru.yandex.webmaster3.storage.host.CommonDataType;
import ru.yandex.webmaster3.storage.settings.dao.CommonDataStateYDao;
import ru.yandex.webmaster3.storage.settings.data.AbstractCommonDataState;
import ru.yandex.webmaster3.storage.turbo.dao.TurboCmntStatisticsYDao;
import ru.yandex.webmaster3.storage.util.yt.AsyncTableReader;
import ru.yandex.webmaster3.storage.util.yt.YtCypressService;
import ru.yandex.webmaster3.storage.util.yt.YtPath;
import ru.yandex.webmaster3.storage.util.yt.YtService;
import ru.yandex.webmaster3.storage.util.yt.YtTableReadDriver;
import ru.yandex.webmaster3.worker.PeriodicTask;
import ru.yandex.webmaster3.worker.TaskSchedule;

/**
 * ishalaru
 * 22.05.2020
 **/
@Slf4j
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
@Service
public class ImportTurboCmntUrlStateTask extends PeriodicTask<ImportTurboCmntUrlStateTask.State> {
    private static final int BATCH_SIZE = 50000;
    @Value("${webmaster3.worker.turbo.cmnt.path:hahn://home/webmaster/prod/turbo/cmnt/}")
    private YtPath path;
    private final CommonDataStateYDao commonDataStateYDao;
    private final YtService ytService;
    private final TurboCmntStatisticsYDao turboCmntStatisticsYDao;

    @Override
    public Result run(UUID runId) throws Exception {
        state = new State();
        DateTime lastImportedDateTime = Optional.ofNullable(commonDataStateYDao.getValue(CommonDataType.TURBO_CMNT_URL_LAST_UPDATE))
                .map(AbstractCommonDataState::getValue)
                .map(e -> DateTime.parse(e, ISODateTimeFormat.dateTime()))
                .orElse(DateTime.parse("2018-01-01"));
        ytService.inTransaction(path).execute(cypressService -> {
            List<YtPath> tableForProcess = findTableToProcess(lastImportedDateTime, cypressService);
            if (tableForProcess.isEmpty()) {
                log.info("Did not find table to process. Last processed table: {}. Finishing task",
                        lastImportedDateTime);
                return false;
            }
            state.countReadTable = tableForProcess.size();
            tableForProcess.sort(Comparator.comparing(YtPath::toString));
            for (YtPath item : tableForProcess) {
                processTable(cypressService, item);
                String date = item.getName().substring(10);
                commonDataStateYDao.update(new CommonDataState(CommonDataType.TURBO_CMNT_URL_LAST_UPDATE, date, DateTime.now()));
                state.countProcessedTable++;
                state.processedTable.add(date);
            }
            return true;
        });
        return Result.SUCCESS;
    }

    private void processTable(YtCypressService cypressService, YtPath tablePath) {

        AsyncTableReader<ImportTurboCmntUrlStateTask.YtRow> tableReader =
                new AsyncTableReader<>(cypressService, tablePath, Range.all(),
                        YtTableReadDriver.createYSONDriver(ImportTurboCmntUrlStateTask.YtRow.class)).splitInParts(100000).withThreadName("turbo-experiments-reader");
        var list = new ArrayList<TurboCmntStatisticsYDao.TurboCmntStatistics>();
        try (AsyncTableReader.TableIterator<ImportTurboCmntUrlStateTask.YtRow> iterator = tableReader.read()) {
            while (iterator.hasNext()) {
                ImportTurboCmntUrlStateTask.YtRow row = iterator.next();
                DateTime startDate = new DateTime(row.startTimestamp / 1000);
                var lStartDate = new LocalDate(startDate.getYear(), startDate.getMonthOfYear(), startDate.getDayOfMonth());
                var builder = TurboCmntStatisticsYDao.TurboCmntStatistics.builder();
                builder.domain(row.domain).url(row.url).startDate(lStartDate).title(row.title).enable(true);
                list.add(builder.build());
                if (list.size() > BATCH_SIZE) {
                    save(list);
                    list.clear();
                }
            }
            save(list);
        } catch (IOException | InterruptedException e) {
            throw new WebmasterException("YT error",
                    new WebmasterErrorResponse.YTServiceErrorResponse(getClass(), e), e);
        }

    }

    private List<YtPath> findTableToProcess(DateTime tableImportedLastTime,
                                            YtCypressService cypressService) {
        return cypressService.list(path)
                .stream()
                .map(YtPath::getName)
                .filter(n -> n.indexOf("increment_") >= 0)
                .filter(n -> DateTime.parse(n.substring(10), ISODateTimeFormat.dateTime()).isAfter(tableImportedLastTime))
                .sorted(Comparator.naturalOrder())
                .map(name -> YtPath.path(path, name))
                .collect(Collectors.toList());
    }

    public void save(List<TurboCmntStatisticsYDao.TurboCmntStatistics> list) {
        turboCmntStatisticsYDao.batchUpdate(list);
    }

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

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

    public static final class YtRow {
        private final String domain;
        private final String url;
        private final long startTimestamp;
        private final long lastUpdateTimestamp;
        private final String title;

        @JsonCreator
        public YtRow(@JsonProperty("domain") String domain,
                     @JsonProperty("url") String url,
                     @JsonProperty("start_date") long startTimestamp,
                     @JsonProperty("update_date") long lastUpdateTimestamp,
                     @JsonProperty("title") String title) {
            this.domain = domain;
            this.url = url;
            this.startTimestamp = startTimestamp;
            this.lastUpdateTimestamp = lastUpdateTimestamp;
            this.title = title == null ? null : title.toLowerCase();
        }
    }


    public static class State implements PeriodicTaskState {
        int countReadTable;
        int countProcessedTable;
        List<String> processedTable = new ArrayList<>();
    }


}
