package ru.yandex.webmaster3.storage.feeds;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;
import org.springframework.stereotype.Repository;

import ru.yandex.webmaster3.core.feeds.feed.FeedServiceType;
import ru.yandex.webmaster3.core.feeds.feed.NativeFeedInfo2;
import ru.yandex.webmaster3.core.feeds.feed.NativeFeedSccStatus;
import ru.yandex.webmaster3.core.feeds.feed.NativeFeedStatus;
import ru.yandex.webmaster3.core.feeds.feed.NativeFeedType;
import ru.yandex.webmaster3.storage.util.ydb.AbstractYDao;
import ru.yandex.webmaster3.storage.util.ydb.querybuilder.typesafe.DataMapper;
import ru.yandex.webmaster3.storage.util.ydb.querybuilder.typesafe.Field;
import ru.yandex.webmaster3.storage.util.ydb.querybuilder.typesafe.Fields;
import ru.yandex.webmaster3.storage.util.ydb.querybuilder.typesafe.ValueDataMapper;


/**
 * @author kravchenko99
 * @date 7/30/21
 */

@Repository
public class FeedsNative2YDao extends AbstractYDao {

    private static final String TABLE_NAME = "feeds_native2";
    public static final String FEEDS_URL_INDEX = "feeds_url_idx";

    public FeedsNative2YDao() {
        super(PREFIX_FEEDS, TABLE_NAME);
    }

    public List<NativeFeedInfo2> list(String domain) {
        return select(MAPPER)
                .where(F.DOMAIN.eq(domain))
                .queryForList(
                        Pair.of(F.DOMAIN, NativeFeedInfo2::getDomain),
                        Pair.of(F.URL, NativeFeedInfo2::getUrl)
                );
    }

    public long count(String domain) {
        return countAll()
                .where(F.DOMAIN.eq(domain))
                .queryOne();
    }

    public void updateError(List<NativeFeedInfo2> items) {
        Lists.partition(items, 2000).forEach(it ->
                batchUpdate(VALUE_MAPPER_UPDATE_ERROR, it).execute());
    }

    public void batchRemove(List<Pair<String, String>> items) {
        Lists.partition(items, 2000).forEach(it ->
                batchDelete(KEY_VALUE_MAPPER, it).execute()
        );
    }

    public NativeFeedInfo2 getPartnerId(String domain, long partnerId) {
        return select(MAPPER)
                .where(F.DOMAIN.eq(domain))
                .and(F.PARTNER_ID.eq(partnerId))
                .queryOne();
    }

    // fullscan костыль на пару дней
    public NativeFeedInfo2 getById(long businessId, long partnerId) {
        return select(MAPPER)
                .where(F.BUSINESS_ID.eq(businessId))
                .and(F.PARTNER_ID.eq(partnerId))
                .queryOne();
    }

    public NativeFeedInfo2 get(String domain, String url) {
        return select(MAPPER)
                .where(F.DOMAIN.eq(domain))
                .and(F.URL.eq(url))
                .queryOne();
    }

    public void update(NativeFeedInfo2 nativeFeedInfo) {
        upsert(VALUE_MAPPER, nativeFeedInfo).execute();
    }

    public void forEach(Consumer<NativeFeedInfo2> consumer) {
        streamReader(MAPPER, consumer, true);
    }

    public boolean hasSuccessfulFeeds(String domain) {
        return select(F.URL)
                .where(F.DOMAIN.eq(domain))
                .and(F.STATUS_SCC.eq(NativeFeedSccStatus.SUCCESS))
                .limit(1)
                .queryOne() != null;
    }

    public List<NativeFeedInfo2> hasSuccessfulGoodsFeeds(List<String> domains) {
        if (domains.isEmpty()) {
            return Collections.emptyList();
        }
        return Lists.partition(domains, 1000).stream().flatMap(it ->
                select(MAPPER)
                        .where(F.DOMAIN.in(domains))
                        .and(F.STATUS_SCC.eq(NativeFeedSccStatus.SUCCESS))
                        .and(F.TYPE.eq(NativeFeedType.STORES))
                        .queryForList(
                                Pair.of(F.DOMAIN, NativeFeedInfo2::getDomain),
                                Pair.of(F.URL, NativeFeedInfo2::getUrl)
                        ).stream()
        ).toList();
    }

    public void remove(String domain, String url) {
        delete().where(F.DOMAIN.eq(domain)).and(F.URL.eq(url)).execute();
    }

    public boolean containsFeed(String url) {
        return select(F.URL).secondaryIndex(FEEDS_URL_INDEX).where(F.URL.eq(url)).queryOne() != null;
    }

    public boolean containsNonGoodsFeed(String url) {
        return select(F.URL).secondaryIndex(FEEDS_URL_INDEX).where(F.URL.eq(url)).and(F.TYPE.ne(NativeFeedType.STORES)).queryOne() != null;
    }

    public void deleteForUser(long userId) {
        var primaryKeys = select(PK_MAPPER).secondaryIndex(USER_ID_INDEX).where(F.USER_ID.eq(userId)).queryForList();
        Lists.partition(primaryKeys, 5000).forEach(b -> {
            batchUpdate(VALUE_MAPPER_SET_USER_ID_TO_NULL, b);
        });
    }

    private static final DataMapper<NativeFeedInfo2> MAPPER = DataMapper.create(
            F.DOMAIN, F.URL, F.USER_ID, F.REGIONS_ID, F.LOGIN, F.PASSWORD,
            F.STATUS, F.ERRORS, F.STATUS_SCC, F.ERRORS_SCC, F.TYPE, F.ENABLED_SERVICE_TYPES, F.ADD_DATE,
            F.UPDATE_DATE, F.ENABLED, F.BUSINESS_ID, F.PARTNER_ID, F.FEED_ID,
            F.SCC_TIMESTAMP, F.ERRORS_OFFER_BASE, F.SCC_FINISH_TIMESTAMP,
            NativeFeedInfo2::new
    );

    private static final ValueDataMapper<NativeFeedInfo2> VALUE_MAPPER = ValueDataMapper.create2(
            Pair.of(F.DOMAIN, NativeFeedInfo2::getDomain),
            Pair.of(F.URL, NativeFeedInfo2::getUrl),
            Pair.of(F.USER_ID, NativeFeedInfo2::getUserId),
            Pair.of(F.REGIONS_ID, NativeFeedInfo2::getRegionsId),
            Pair.of(F.STATUS, NativeFeedInfo2::getStatus),
            Pair.of(F.STATUS_SCC, NativeFeedInfo2::getStatusScc),
            Pair.of(F.ERRORS, NativeFeedInfo2::getErrors),
            Pair.of(F.ERRORS_SCC, NativeFeedInfo2::getErrorsScc),
            Pair.of(F.TYPE, NativeFeedInfo2::getType),
            Pair.of(F.ENABLED_SERVICE_TYPES, NativeFeedInfo2::getEnabledServiceTypes),
            Pair.of(F.ADD_DATE, NativeFeedInfo2::getAddDate),
            Pair.of(F.UPDATE_DATE, NativeFeedInfo2::getUpdateDate),
            Pair.of(F.ENABLED, NativeFeedInfo2::isEnabled),
            Pair.of(F.LOGIN, NativeFeedInfo2::getLogin),
            Pair.of(F.PASSWORD, NativeFeedInfo2::getPassword),

            Pair.of(F.BUSINESS_ID, NativeFeedInfo2::getBusinessId),
            Pair.of(F.PARTNER_ID, NativeFeedInfo2::getPartnerId),
            Pair.of(F.FEED_ID, NativeFeedInfo2::getFeedId),

            Pair.of(F.SCC_TIMESTAMP, NativeFeedInfo2::getSccTimestamp),
            Pair.of(F.ERRORS_OFFER_BASE, NativeFeedInfo2::getErrorsOfferBase),
            Pair.of(F.SCC_FINISH_TIMESTAMP, NativeFeedInfo2::getSccFinishTimestamp)
    );

    private static final ValueDataMapper<Pair<String, String>> KEY_VALUE_MAPPER = ValueDataMapper.create2(
            Pair.of(F.DOMAIN, Pair::getLeft),
            Pair.of(F.URL, Pair::getRight)
    );

    private static final DataMapper<Pair<String, String>> PK_MAPPER = DataMapper.create(
            F.DOMAIN, F.URL, Pair::of
    );

    private static final ValueDataMapper<NativeFeedInfo2> VALUE_MAPPER_UPDATE_ERROR = ValueDataMapper.create2(
            Pair.of(F.DOMAIN, NativeFeedInfo2::getDomain),
            Pair.of(F.URL, NativeFeedInfo2::getUrl),
            Pair.of(F.STATUS, NativeFeedInfo2::getStatus),
            Pair.of(F.ERRORS_OFFER_BASE, NativeFeedInfo2::getErrorsOfferBase)
    );

    private static final ValueDataMapper<Pair<String, String>> VALUE_MAPPER_SET_USER_ID_TO_NULL = ValueDataMapper.create2(
            Pair.of(F.DOMAIN, Pair::getLeft),
            Pair.of(F.URL, Pair::getRight),
            Pair.of(F.USER_ID, ign -> null)
    );

    private interface F {
        TypeReference<List<Map<String, Object>>> LIST = new TypeReference<>() {
        };
        TypeReference<List<Integer>> LIST_INT = new TypeReference<>() {
        };

        TypeReference<List<String>> LIST_STR = new TypeReference<>() {
        };
        //key
        Field<String> DOMAIN = Fields.stringField("domain");
        Field<String> URL = Fields.stringField("url");

        Field<List<Integer>> REGIONS_ID = Fields.jsonField2("regions_id", LIST_INT);
        Field<Long> USER_ID = Fields.longField("user_id").makeOptional();
        Field<String> LOGIN = Fields.stringField("login").makeOptional();
        Field<String> PASSWORD = Fields.stringField("password").makeOptional();
        Field<NativeFeedStatus> STATUS = Fields.intEnumField("status", NativeFeedStatus.R);
        Field<List<String>> ERRORS = Fields.jsonField2("errors",LIST_STR);
        Field<NativeFeedSccStatus> STATUS_SCC = Fields.intEnumField("status_scc", NativeFeedSccStatus.R);
        Field<List<String>> ERRORS_SCC = Fields.jsonField2("errors_scc", LIST_STR);
        Field<NativeFeedType> TYPE = Fields.intEnumField("type", NativeFeedType.R);
        Field<Set<FeedServiceType>> ENABLED_SERVICE_TYPES = Fields.jsonField2("enabled_service_types",
                FeedServiceType.SET_R);

        Field<DateTime> ADD_DATE = Fields.jodaDateTimeField("add_date");
        Field<DateTime> UPDATE_DATE = Fields.jodaDateTimeField("update_date");
        Field<Boolean> ENABLED = Fields.boolField("enabled");
        // для mbi
        Field<Long> BUSINESS_ID = Fields.longField("business_id").makeOptional();
        Field<Long> PARTNER_ID = Fields.longField("partner_id").makeOptional();
        Field<Long> FEED_ID = Fields.longField("feed_id").makeOptional();
        // для us
        Field<DateTime> SCC_TIMESTAMP = Fields.jodaDateTimeField("scc_timestamp").makeOptional();
        Field<List<Map<String, Object>>> ERRORS_OFFER_BASE = Fields.listField("errors_offer_base", LIST).makeOptional();
        Field<DateTime> SCC_FINISH_TIMESTAMP = Fields.jodaDateTimeField("scc_finish_timestamp").makeOptional();
    }
}
