package ru.yandex.webmaster3.worker.download;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;
import org.joda.time.Instant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import ru.yandex.common.util.concurrent.ThreadFactories;
import ru.yandex.webmaster3.core.download.ExportUtil;
import ru.yandex.webmaster3.core.download.MdsSerializable;
import ru.yandex.webmaster3.core.worker.task.TaskResult;
import ru.yandex.webmaster3.storage.util.ydb.exception.WebmasterYdbException;
import ru.yandex.webmaster3.storage.download.DownloadInfo;
import ru.yandex.webmaster3.storage.download.DownloadInfoYDao;
import ru.yandex.webmaster3.storage.download.DownloadStatus;
import ru.yandex.webmaster3.storage.download.MDSService;
import ru.yandex.webmaster3.storage.download.common.DownloadFileType;
import ru.yandex.webmaster3.storage.download.common.MdsExportTaskData;
import ru.yandex.webmaster3.worker.ProcessingCountLimitedTask;

/**
 * @author avhaliullin
 */
@Slf4j
public class MdsExportTask extends ProcessingCountLimitedTask<MdsExportTaskData> {
    private final int MAX_PROCESSING_TIME_IN_HOURS = 1;
    private final DownloadInfoYDao downloadInfoYDao;
    private final MDSService mdsService;
    private Map<DownloadFileType, AbstractMdsDataProvider> providersMap;
    private ExecutorService executors;

    @Autowired
    public MdsExportTask(
            DownloadInfoYDao downloadInfoYDao,
            MDSService mdsService,
            @Qualifier("mdsProvidersMap") Map<DownloadFileType, AbstractMdsDataProvider> providersMap
    ) {
        this.downloadInfoYDao = downloadInfoYDao;
        this.mdsService = mdsService;
        this.providersMap = providersMap;
        executors = Executors.newFixedThreadPool(16,
                ThreadFactories.newThreadFactory(true, "mds-upload-thread", Thread.NORM_PRIORITY));
    }

    @Override
    public Result run(MdsExportTaskData data) throws Exception {

        AbstractMdsDataProvider<?> mdsDataProvider = providersMap.get(data.getDescriptor().getType());
        if (mdsDataProvider != null) {
            uploadToMds(data, mdsDataProvider);
        } else {
            throw new RuntimeException("Unknown descriptor " + data.getDescriptor());
        }

        return new Result(TaskResult.SUCCESS);
    }

    private <T extends MdsSerializable> void uploadToMds(MdsExportTaskData data, AbstractMdsDataProvider<T> mdsDataProvider) {
        var exportFormat = data.getExportFormat();

        try (var writer = ExportUtil.createWriter(exportFormat, mdsDataProvider.getRowClass())) {
            var fileName = data.getFileName() + exportFormat.getExtension();
            final CompletableFuture<Void> dataWriter = CompletableFuture.runAsync(() -> {
                try {
                    mdsDataProvider.provide(data, writer::write);
                    writer.allDataUploaded();
                } catch (Exception e) {
                    try {
                        writer.close();
                    } catch (IOException ex) {
                        throw new RuntimeException(ex);
                    }
                    log.error(e.getMessage(), e);
                    throw new RuntimeException(e);
                }
            }, executors);
            // remove file if needed
            DownloadInfo existingInfo = downloadInfoYDao.searchByFileName(fileName);
            if (existingInfo != null) {
                mdsService.deleteFile(existingInfo);
            }
            final CompletableFuture<String> dataUpload =
                    CompletableFuture.supplyAsync(() -> mdsService.uploadFileAndGetDownloadLink(writer.inputStream(), fileName), executors);
            dataUpload.get(MAX_PROCESSING_TIME_IN_HOURS, TimeUnit.HOURS);
            dataWriter.join();
            writer.close();
            downloadInfoYDao.add(data.getHostId(), data.getHash(), DownloadInfo.success(fileName, dataUpload.get()));
        } catch (Exception e) {
            log.error("Error uploading data to MDS", e);
            try {
                downloadInfoYDao.add(data.getHostId(), data.getHash(), DownloadInfo.error());
            } catch (WebmasterYdbException e1) {
                log.error("Failed to store task status", e1);
            }

            throw new RuntimeException(e);
        }
    }

    @Override
    public Class<MdsExportTaskData> getDataClass() {
        return MdsExportTaskData.class;
    }
}
