package ru.yandex.webmaster3.worker.download.memorandum;

import java.io.IOException;
import java.util.function.Predicate;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.joda.time.Instant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.core.util.functional.ThrowingConsumer;
import ru.yandex.webmaster3.storage.download.common.MdsExportTaskData;
import ru.yandex.webmaster3.storage.download.common.MemorandumUrlsMdsExportDescriptor;
import ru.yandex.webmaster3.storage.host.CommonDataType;
import ru.yandex.webmaster3.storage.searchurl.samples.dao.MemorandumConditions;
import ru.yandex.webmaster3.storage.settings.SettingsService;
import ru.yandex.webmaster3.storage.util.StructureFilterService;
import ru.yandex.webmaster3.storage.util.clickhouse2.condition.ConditionFieldExtractor;
import ru.yandex.webmaster3.storage.util.yt.AsyncTableReader;
import ru.yandex.webmaster3.storage.util.yt.YtPath;
import ru.yandex.webmaster3.storage.util.yt.YtService;
import ru.yandex.webmaster3.storage.util.yt.YtTableRange;
import ru.yandex.webmaster3.storage.util.yt.YtTableReadDriver;
import ru.yandex.webmaster3.worker.download.AbstractMdsDataProvider;
import ru.yandex.webmaster3.worker.memorandum.MemorandumYTUtils;
import ru.yandex.webmaster3.worker.memorandum.MemorandumYtTableType;

import static ru.yandex.webmaster3.core.util.functional.ThrowingConsumer.rethrowingUnchecked;

/**
 * @author leonidrom
 */
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class MemorandumMdsDataProvider extends AbstractMdsDataProvider<MemorandumSpreadsheetRow> {
    private static final Logger log = LoggerFactory.getLogger(MemorandumMdsDataProvider.class);

    private final SettingsService settingsService;
    private final StructureFilterService structureFilterService;
    private final YtService ytService;
    @Setter
    private YtPath mdbMemorandumImportDir;

    @Override
    public void provide(MdsExportTaskData exportTaskData, ThrowingConsumer<MemorandumSpreadsheetRow, Exception> consumer) throws Exception {
        var exportDescriptor = (MemorandumUrlsMdsExportDescriptor) exportTaskData.getDescriptor();
        var condition = MemorandumConditions.makeCondition(structureFilterService,
                exportTaskData.getHostId(), exportDescriptor.getNodeId(), exportDescriptor.getFilters()
        );
        Predicate<MemorandumYtRow> rowPredicate = condition.toPredicate(MEMORANDUM_FIELD_EXTRACTOR);

        YtPath tablePath = YtPath.path(mdbMemorandumImportDir, MemorandumYTUtils.makeTableName(
                settingsService.getSettingCached(CommonDataType.MDB_MEMORANDUM_DATA_VERSION, v -> new Instant(Long.parseLong(v))),
                MemorandumYtTableType.ALL_URLS
        ));
        ytService.withoutTransaction(cypressService -> {
            var tableReader = new AsyncTableReader<>(
                    cypressService, tablePath, YtTableRange.singleKey(exportTaskData.getHostId().toStringId()),
                    YtTableReadDriver.createYSONDriver(MemorandumYtRow.class)
            );

            try (var it = tableReader.read()) {
                while (it.hasNext()) {
                    MemorandumYtRow row = it.next();
                    if (exportTaskData.getHostId().toStringId().equals(row.hostId) && rowPredicate.test(row)) {
                        MemorandumSpreadsheetRow ssRow;
                        try {
                            ssRow = row.toSpreadsheet();
                        } catch (Exception e) {
                            log.error("Bad row", e);
                            continue;
                        }

                        rethrowingUnchecked(consumer).accept(ssRow);
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            return true;
        });
    }

    @Override
    public Class<MemorandumSpreadsheetRow> getRowClass() {
        return MemorandumSpreadsheetRow.class;
    }

    private static class MemorandumYtRow {
        @JsonProperty("HostId")
        private final String hostId;
        @JsonProperty("Path")
        private final String path;
        @JsonProperty("CopyrightOwner")
        private final String copyrightOwner;
        @JsonProperty("CopyrightObject")
        private final String copyrightObject;

        @JsonCreator
        public MemorandumYtRow(String hostId, String path, String copyrightOwner, String copyrightObject) {
            this.hostId = hostId;
            this.path = path;
            this.copyrightOwner = copyrightOwner;
            this.copyrightObject = copyrightObject;
        }

        MemorandumSpreadsheetRow toSpreadsheet() {
            WebmasterHostId hostId = IdUtils.stringToHostId(this.hostId);
            return new MemorandumSpreadsheetRow(
                    IdUtils.hostIdToUrl(hostId) + path,
                    copyrightObject, copyrightOwner
            );
        }
    }

    private static final ConditionFieldExtractor<MemorandumYtRow> MEMORANDUM_FIELD_EXTRACTOR = new ConditionFieldExtractor<MemorandumYtRow>() {
        @Override
        protected Object extractRaw(MemorandumYtRow object, String fieldName) {
            switch (fieldName) {
                case "path":
                    return object.path;
                case "copyright_owner":
                    return object.copyrightOwner;
                case "copyright_object":
                    return object.copyrightObject;
                default:
                    throw new IllegalArgumentException("Unknown field " + fieldName);
            }
        }
    };
}
