package ru.yandex.autotests.direct.db.steps;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import javax.annotation.Nullable;

import org.jooq.util.mysql.MySQLDSL;

import ru.yandex.autotests.direct.db.models.jooq.ppc.enums.BannersStatusbssynced;
import ru.yandex.autotests.direct.db.models.jooq.ppc.enums.BsExportSpecialsParType;
import ru.yandex.autotests.direct.db.models.jooq.ppc.enums.CampaignsStatusbssynced;
import ru.yandex.autotests.direct.db.models.jooq.ppc.enums.CampaignsType;
import ru.yandex.autotests.direct.db.models.jooq.ppc.enums.PhrasesStatusbssynced;
import ru.yandex.autotests.direct.db.models.jooq.ppc.tables.records.BsExportQueueRecord;
import ru.yandex.autotests.direct.db.models.jooq.ppc.tables.records.BsExportSpecialsRecord;
import ru.yandex.autotests.direct.db.steps.base.BasePpcSteps;
import ru.yandex.autotests.direct.db.steps.base.LimitOffsetStepCallable;
import ru.yandex.qatools.allure.annotations.Step;

import static ru.yandex.autotests.direct.db.models.jooq.ppc.Tables.BS_EXPORT_SPECIALS;
import static ru.yandex.autotests.direct.db.models.jooq.ppc.tables.Banners.BANNERS;
import static ru.yandex.autotests.direct.db.models.jooq.ppc.tables.Bids.BIDS;
import static ru.yandex.autotests.direct.db.models.jooq.ppc.tables.BidsDynamic.BIDS_DYNAMIC;
import static ru.yandex.autotests.direct.db.models.jooq.ppc.tables.BidsPerformance.BIDS_PERFORMANCE;
import static ru.yandex.autotests.direct.db.models.jooq.ppc.tables.BsExportQueue.BS_EXPORT_QUEUE;
import static ru.yandex.autotests.direct.db.models.jooq.ppc.tables.Campaigns.CAMPAIGNS;
import static ru.yandex.autotests.direct.db.models.jooq.ppc.tables.Phrases.PHRASES;


/*
* todo javadoc
*/
public class TransportSteps extends BasePpcSteps {
    public static final int ITERATION_LIMIT = 100;

    @Step("Получение списка ID кампаний по условию с offset:{1}, limit:{2}")
    public List<Long> getCampaignsIds(LimitOffsetStepCallable<List<Long>> callable, int offset, int limit) {
        return exec(db -> callable.run(db, limit, offset));
    }

    @Step("Получение списка ID кампаний по условию с limit:{1}, групп не более {2}, условий показа не более {3}, баннеров не более {4}")
    public List<Long> getCampaignsIds(LimitOffsetStepCallable<List<Long>> callable, int limit, int maxPhrases, int maxBids, int maxBanners) {
        return getCampaignsIds(callable, 0, limit, maxPhrases, maxBids, maxBanners);
    }


    /**
     * Find limited amount of cids for campaigns which suits condition provided in callable
     * with limit to total adgroups, banners and bids(except retargeting) amounts.
     * <p>
     * We don't want to overload DB, so we use batch select with stepping offset.
     * Step equals to limit. Max steps amount: ITERATION_LIMIT.
     *
     * @return The resulting campaignList with no more than limit size(empty in worst case).
     */

    @Step("Получение списка ID кампаний по условию с limit:{2}, групп не более {3}, условий показа не более {4}, баннеров не более {5}")
    public List<Long> getCampaignsIds(LimitOffsetStepCallable<List<Long>> callable, int offset, int limit,
                                      int maxPhrases, int maxBids, int maxBanners) {
        int iterationNumber = 0;
        int currentLimit = limit;
        int currentOffset = offset;

        Set<Long> cidSet = new HashSet<>();
        List<Long> batchCidList = getCampaignsIds(callable, currentOffset, currentLimit);
        while ((iterationNumber < ITERATION_LIMIT) && (!batchCidList.isEmpty())) {
            batchCidList.stream()
                    .filter(cid -> campaignIsSuitable(cid, maxPhrases, maxBids, maxBanners))
                    .forEach(cidSet::add);
            currentOffset += currentLimit;
            currentLimit = limit - cidSet.size();
            batchCidList = getCampaignsIds(callable, currentOffset, currentLimit);
            iterationNumber++;
        }
        return new ArrayList<>(cidSet);
    }

    /**
     * Check whether campaign with given cid is suitable for using in b2b-tests
     */
    private boolean campaignIsSuitable(Long cid, int maxPhrases, int maxBids, int maxBanners) {
        List<Long> pids = exec(db -> db.select(PHRASES.PID)
                .from(PHRASES)
                .where(PHRASES.CID.eq(cid))
                .fetch(PHRASES.PID));
        if (pids.size() <= maxPhrases) {
            Integer bannersAmount = exec(db -> db.selectCount()
                    .from(BANNERS)
                    .where(BANNERS.PID.in(pids))
                    .fetchOne(0, int.class));
            if (bannersAmount <= maxBanners) {
                CampaignsType type = exec(db -> db.select(CAMPAIGNS.TYPE)
                        .from(CAMPAIGNS)
                        .where(CAMPAIGNS.CID.eq(cid))
                        .fetchOne(CAMPAIGNS.TYPE));
                Integer bidsAmount;
                switch (type) {
                    case dynamic:
                        bidsAmount = exec(db -> db.selectCount()
                                .from(BIDS_DYNAMIC)
                                .where(BIDS_DYNAMIC.PID.in(pids))
                                .fetchOne(0, int.class));
                        break;
                    case performance:
                        bidsAmount = exec(db -> db.selectCount()
                                .from(BIDS_PERFORMANCE)
                                .where(BIDS_PERFORMANCE.PID.in(pids))
                                .fetchOne(0, int.class));
                        break;
                    default:
                        bidsAmount = exec(db -> db.selectCount()
                                .from(BIDS)
                                .where(BIDS.PID.in(pids))
                                .fetchOne(0, int.class));
                        break;
                }
                if (bidsAmount <= maxBids) {
                    return true;
                }
            }
        }
        return false;
    }

    @Step("Выставление статуса синхронизации с БК \"{1}\" для кампаний cids:{0}")
    public void setCampaignStatusBsSynced(List<Long> cids, CampaignsStatusbssynced status) {
        run(db -> db.update(CAMPAIGNS)
                .set(CAMPAIGNS.STATUSBSSYNCED, status)
                .where(CAMPAIGNS.CID.in(cids))
                .execute()
        );
    }

    @Step("Выставление статуса синхронизации с БК \"{1}\" для групп pids:{0}")
    public void setPhrasesStatusBsSynced(List<Long> pids, PhrasesStatusbssynced status) {
        run(db -> db.update(PHRASES)
                .set(PHRASES.STATUSBSSYNCED, status)
                .where(PHRASES.PID.in(pids))
                .execute()
        );
    }

    @Step("Выставление статуса синхронизации с БК \"{1}\" для баннеров в группах pids:{0}")
    public void setBannersStatusBsSynced(List<Long> pids, BannersStatusbssynced status) {
        run(db -> db.update(BANNERS)
                .set(BANNERS.STATUSBSSYNCED, status)
                .where(BANNERS.PID.in(pids))
                .execute()
        );
    }

    @Step("DB: получение записи из таблицы ppc.bs_export_queue, cid = {0}")
    public BsExportQueueRecord getBsExportQueueRecord(Long cid) {
        return exec(db -> db.selectFrom(BS_EXPORT_QUEUE)
                .where(BS_EXPORT_QUEUE.CID.eq(cid))
                .fetchOne());
    }

    @Step("DB: получение количества записей в таблицe ppc.bs_export_queue")
    public Long getBsExportQueueSize() {
        return exec(db -> db.selectCount()
                .from(BS_EXPORT_QUEUE)
                .fetchOne(0, long.class));
    }

    @Step("DB: добавление записи в таблицу ppc.bs_export_queue")
    public void createBsExportQueueRecord(BsExportQueueRecord bsExportQueueRecord) {
        exec(db -> db.insertInto(BS_EXPORT_QUEUE)
                .set(bsExportQueueRecord)
                .execute());
    }

    @Step("DB: добавление записи в таблицу ppc.bs_export_queue")
    public void updateBsExportQueueRecord(BsExportQueueRecord bsExportQueueRecord) {
        exec(db -> db.update(BS_EXPORT_QUEUE)
                .set(bsExportQueueRecord)
                .execute());
    }

    @Step("DB: кладем кампанию в bs_export_specials: cid = {0}, par_type = {1}")
    public void putCampInBsExportSpecials(Long cid, BsExportSpecialsParType parType) {
        BsExportSpecialsRecord record = new BsExportSpecialsRecord()
                .setCid(cid)
                .setParType(parType);
        run(db -> db.insertInto(BS_EXPORT_SPECIALS)
                .set(record)
                .onDuplicateKeyUpdate().set(BS_EXPORT_SPECIALS.PAR_TYPE, MySQLDSL.values(BS_EXPORT_SPECIALS.PAR_TYPE))
                .execute()
        );
    }

    /**
     * @return par_type, если кампания есть в спец. очереди. Иначе - {@code null}
     */
    @Step("DB: получение par_type из таблицы ppc.bs_export_specials, cid = {0}")
    public @Nullable BsExportSpecialsParType getCampBsExportSpecialsParType(Long cid) {
        Optional<BsExportSpecialsParType> parType = exec(db -> db.select(BS_EXPORT_SPECIALS.PAR_TYPE)
                .from(BS_EXPORT_SPECIALS)
                .where(BS_EXPORT_SPECIALS.CID.eq(cid))
                .fetchOptional(BS_EXPORT_SPECIALS.PAR_TYPE)
        );

        return parType.orElse(null);
    }

}
