package ru.yandex.webmaster3.storage.turbo.service.adv;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.blackbox.UserWithLogin;
import ru.yandex.webmaster3.core.blackbox.service.BlackboxUsersService;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.turbo.adv.AdvertisingIntegrationService;
import ru.yandex.webmaster3.core.turbo.adv.model.AdvBlock;
import ru.yandex.webmaster3.core.turbo.adv.model.AdvDomain;
import ru.yandex.webmaster3.core.turbo.adv.model.AdvPage;
import ru.yandex.webmaster3.core.turbo.adv.model.AdvUpdateBlock;
import ru.yandex.webmaster3.core.turbo.adv.model.AdvUser;
import ru.yandex.webmaster3.core.turbo.adv.model.BlockAttributes;
import ru.yandex.webmaster3.core.turbo.adv.model.IAdvRequest;
import ru.yandex.webmaster3.core.turbo.adv.model.response.ResponseCreate;
import ru.yandex.webmaster3.core.turbo.adv.model.response.ResponseFind;
import ru.yandex.webmaster3.core.turbo.model.TurboHostSettings;
import ru.yandex.webmaster3.core.turbo.model.advertising.AdvertisingNetworkType;
import ru.yandex.webmaster3.core.turbo.model.advertising.AdvertisingPlacement;
import ru.yandex.webmaster3.core.turbo.model.advertising.AdvertisingSettings;
import ru.yandex.webmaster3.core.util.WwwUtil;
import ru.yandex.webmaster3.core.util.json.JsonMapping;
import ru.yandex.webmaster3.storage.abt.AbtService;
import ru.yandex.webmaster3.storage.abt.model.Experiment;
import ru.yandex.webmaster3.storage.turbo.dao.adv.TurboAdvBlockPostponedYDao;
import ru.yandex.webmaster3.storage.turbo.dao.adv.TurboAutoAdvYDao;
import ru.yandex.webmaster3.storage.turbo.service.settings.TurboSettingsService;

import static ru.yandex.webmaster3.storage.turbo.dao.adv.TurboAdvBlockPostponedYDao.Status.WAITING_EXTERNAL_USER_APPROVE;

/**
 * ishalaru
 * 02.09.2020
 **/
@Service
@Slf4j
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
public class TurboAutoAdvService {
    private static final List<String> DSP_BLOCK_MULTI_FORMAT = List.of("100%x250", "300x250", "300x300", "336x280");
    private static final List<String> DSP_BLOCK_SINGLE = List.of("320x50");

    public static final String HORIZONTAL = "horizontal";
    public static final String VERTICAL = "vertical";

    private final BlackboxUsersService blackboxExternalYandexUsersService;
    private final TurboAutoAdvYDao turboAutoAdvYDao;
    private final TurboAdvBlockPostponedYDao turboAdvBlockPostponedYDao;
    private final AdvertisingIntegrationService advertisingIntegrationService;
    private final TurboSettingsService turboSettingsService;
    private final AbtService abtService;

    public AutoAdvStatus initTurboAutoAdvBlocks(String uid, WebmasterHostId hostId) {
        String domain = WwwUtil.cutWWWAndM(hostId.getPunycodeHostname());
        return initTurboAutoAdvBlocks(uid, domain);
    }

    public AutoAdvStatus initTurboAutoAdvBlocks(String uid, String domain) {
        log.info("initTurboAutoAdvBlocks: {}, {}", uid, domain);
        TurboAutoAdvYDao.InformationBlock informationBlock = turboAutoAdvYDao.loadBlock(domain);
        if (informationBlock == null) {
            log.info("initTurboAutoAdvBlocks: no blocks");
            final UserWithLogin user = blackboxExternalYandexUsersService.getUserById(Long.parseLong(uid));
            String domainId = getDomainId(user.getLogin(), domain);
            if (domainStatusIsValid(domainId)) {
                log.info("initTurboAutoAdvBlocks: domain is valid");
                String pageId = getPageId(user.getLogin(), domain, Long.valueOf(domainId));
                boolean mediaInDesign = abtService.isInExperiment(user.getUserId(), Experiment.PI_MEDIA_IN_DESIGNS_FEATURES);
                log.info("initTurboAutoAdvBlocks: will create adv blocks");
                informationBlock = createAdvBlock(mediaInDesign, pageId);
                log.info("initTurboAutoAdvBlocks: created adv blocks");

                update(domain, informationBlock);
                turboAdvBlockPostponedYDao.delete(domain);
                turboAutoAdvYDao.save(domain, informationBlock, AutoAdvStatus.RSI_BLOCKS_ENABLED, TurboAdvStatus.AUTO, DateTime.now());
            } else {
                log.info("initTurboAutoAdvBlocks: domain is not valid, will wait for validation");
                //put page in async process.
                turboAutoAdvYDao.save(domain, null, AutoAdvStatus.WAIT_DOMAIN_VALIDATION, TurboAdvStatus.AUTO, DateTime.now());
                turboAdvBlockPostponedYDao.insert(domain, TurboAdvBlockPostponedYDao.Status.WAITING_DOMAIN_VALIDATION, new TurboAdvBlockPostponedYDao.PostponeData(null, uid, null));
                return AutoAdvStatus.WAIT_DOMAIN_VALIDATION;
            }
        } else {
            log.info("initTurboAutoAdvBlocks: will update blocks");
            boolean mediaInDesign = abtService.isInExperiment(Long.parseLong(uid), Experiment.PI_MEDIA_IN_DESIGNS_FEATURES);
            updateBlock(mediaInDesign, informationBlock);
            update(domain, informationBlock);
            turboAutoAdvYDao.save(domain, informationBlock, AutoAdvStatus.RSI_BLOCKS_ENABLED, TurboAdvStatus.AUTO, DateTime.now());
        }

        return AutoAdvStatus.RSI_BLOCKS_ENABLED;
    }

    public void waitUserEmailVerification(WebmasterHostId hostId, String email, String key) {
        String domain = WwwUtil.cutWWWAndM(hostId.getPunycodeHostname());
        waitUserEmailVerification(domain, email, key);
    }

    public void waitUserEmailVerification(String domain, String email, String key) {
        this.setStatus(domain, AutoAdvStatus.WAIT_EXTERNAL_USER_VERIFICATION, TurboAdvStatus.AUTO);
        turboAdvBlockPostponedYDao.insert(domain, WAITING_EXTERNAL_USER_APPROVE, new TurboAdvBlockPostponedYDao.PostponeData(email, null, key));
    }

    public boolean userIsExists(Long uid) {
        final ResponseFind response = advertisingIntegrationService.find(new AdvUser(uid));
        return response.getErrors() == null &&
                response.getData() != null &&
                !response.getData().isEmpty() &&
                response.getData().get(0).getId().equals(String.valueOf(uid));
    }

    public Pair<AutoAdvStatus, TurboAdvStatus> loadStatus(WebmasterHostId hostId) {
        String domain = WwwUtil.cutWWWAndM(hostId.getPunycodeHostname());
        return loadStatus(domain);
    }

    public Pair<AutoAdvStatus, TurboAdvStatus> loadStatus(String domain) {
        return turboAutoAdvYDao.loadStatus(domain);
    }

    public void resetStatus(WebmasterHostId hostId) {
        resetStatus(WwwUtil.cutWWWAndM(hostId.getPunycodeHostname()));
    }

    public void resetStatus(String domain) {
        turboAutoAdvYDao.delete(domain);
        turboAdvBlockPostponedYDao.delete(domain);
    }

    public void changeStateToAuto(String domain) {
        turboAutoAdvYDao.save(domain, null, AutoAdvStatus.USER_SELECT_AUTO_ADV, TurboAdvStatus.AUTO, DateTime.now());
    }


    public void createAutoADVStart(WebmasterHostId hostId) {
        String domain = WwwUtil.cutWWWAndM(hostId.getPunycodeHostname());
        turboAutoAdvYDao.save(domain, null, AutoAdvStatus.CREATION_IN_PROGRESS, TurboAdvStatus.AUTO, DateTime.now());
    }

    public void setStatus(String domain, AutoAdvStatus status, TurboAdvStatus turboAdvStatus) {
        turboAutoAdvYDao.save(domain, null, status, turboAdvStatus, DateTime.now());
    }

    public TurboAdvBlockPostponedYDao.Status autoAdvertisingStatus(WebmasterHostId hostId) {
        String domain = WwwUtil.cutWWWAndM(hostId.getPunycodeHostname());
        final TurboAdvBlockPostponedYDao.AdvPostponedInfo load = turboAdvBlockPostponedYDao.load(domain);
        return load.getStatus();
    }

    public void createAdvAfterDomainValidation(TurboAdvBlockPostponedYDao.AdvPostponedInfo info) {
        log.info("createAdvAfterDomainValidation: {}, {}", info.getData().getUserId(), info.getDomain());
        final UserWithLogin user = blackboxExternalYandexUsersService.getUserById(Long.valueOf(info.getData().getUserId()));
        ResponseFind response = advertisingIntegrationService.find(new AdvDomain(info.getDomain(), user.getLogin()));
        log.info("createAdvAfterDomainValidation: domain find response: {}", response);
        if (response.getErrors() != null
                || response.getData() == null
                || response.getData().isEmpty()) {
            log.info("createAdvAfterDomainValidation: no domain data");
            return;
        }

        String domainId = response.getData().get(0).getId();
        if (domainStatusIsValid(domainId)) {
            log.info("createAdvAfterDomainValidation: domain is valid");
            String pageId = getPageId(user.getLogin(), info.getDomain(), Long.valueOf(domainId));
            boolean mediaInDesign = abtService.isInExperiment(Long.parseLong(info.getData().getUserId()), Experiment.PI_MEDIA_IN_DESIGNS_FEATURES);

            log.info("createAdvAfterDomainValidation: will create adv blocks");
            final TurboAutoAdvYDao.InformationBlock blocks = createAdvBlock(mediaInDesign, pageId);
            log.info("createAdvAfterDomainValidation: created adv blocks");

            update(info.getDomain(), blocks);
            turboAdvBlockPostponedYDao.delete(info.getDomain());
            turboAutoAdvYDao.save(info.getDomain(), blocks, AutoAdvStatus.RSI_BLOCKS_ENABLED, TurboAdvStatus.AUTO, DateTime.now());
        } else {
            log.info("createAdvAfterDomainValidation: domain is not valid");
        }
    }

    private boolean domainStatusIsValid(String domainId) {
        final String STATE = "multistate";
        final String VALID_MULTISTATE = "4";
        final String TYPE = "site";
        log.info("domainStatusIsValid: {}", domainId);
        final ResponseCreate responseFind = advertisingIntegrationService.find(domainId, TYPE, STATE);
        log.info("domainStatusIsValid: find response: {}", responseFind);

        return responseFind.getErrors() == null &&
                responseFind.getData() != null &&
                VALID_MULTISTATE.equals(responseFind.getData().getAttributes().get(STATE));
    }

    private void update(String domain, TurboAutoAdvYDao.InformationBlock informationBlock) {
        final TurboHostSettings settings = turboSettingsService.getSettings(domain);
        final List<AdvertisingSettings> advertisingSettings = new ArrayList<>(4);
        for (Map.Entry<AdvertisingPlacement, String> entry : informationBlock.getBlocks().entrySet()) {
            final ObjectNode jsonNodes = new ObjectNode(new JsonNodeFactory(true));
            jsonNodes.put("id", entry.getValue());
            final AdvertisingSettings turboTopAutoAdv = new AdvertisingSettings(AdvertisingNetworkType.YANDEX, entry.getKey(), "TURBO AUTO ADV", entry.getValue(), jsonNodes);
            advertisingSettings.add(turboTopAutoAdv);
        }
        final TurboHostSettings.TurboHostSettingsBuilder turboHostSettingsBuilder = new TurboHostSettings.TurboHostSettingsBuilder(settings);
        turboHostSettingsBuilder.setAdvertisingSettings(advertisingSettings);
        turboSettingsService.updateSettings(domain, turboHostSettingsBuilder.build());
    }


    private String getPageId(String login, String domain, Long domainId) {
        return getId(new AdvPage(domain, domainId, login, "Turbo autogenerated page"));
    }

    private String getDomainId(String login, String domain) {
        return getId(new AdvDomain(domain, login));
    }

    private static BlockAttributes.BlockAttributesBuilder createAdvBlockBuilder(boolean mediaInDesign, List<String> dspBlock, List<String> filterTags) {
        final BlockAttributes.Template template = BlockAttributes.Template.builder()
                .caption("Common design")
                .filterTags(filterTags)
                .isCustom(false)
                .settings(BlockAttributes.TGASetting.builder().limit(1).name("adaptive0418").build())
                .type("tga")
                .build();
        final BlockAttributes.Template mediaTemplate = BlockAttributes.Template.builder()
                .caption("Media adv")
                .filterTags(filterTags)
                .isCustom(false)
                .settings(BlockAttributes.MediaSettings.builder().filterSizes(false).horizontalAlign(true).build())
                .type("media")
                .build();
        final BlockAttributes.BlockAttributesBuilder turbo = BlockAttributes.builder()
                .blind(0)
                .strategy(1l)
                .dspBlocks(dspBlock)
                .templates(List.of(template))
                .siteVersion("turbo")
                .caption("Turbo mobile autogenerated: " + filterTags.toString());
        if (mediaInDesign) {
            turbo.templates(List.of(template, mediaTemplate));
        }
        return turbo;

    }

    private BlockAttributes updateBlock(boolean mediaInDesign, List<String> dspBlock, List<String> filterTags) {
        return createAdvBlockBuilder(mediaInDesign, dspBlock, filterTags).build();
    }

    public static BlockAttributes createBlock(boolean mediaInDesign, String pageId, List<String> dspBlock, List<String> filterTags) {
        return createAdvBlockBuilder(mediaInDesign, dspBlock, filterTags)
                .pageId(Long.valueOf(pageId))
                .build();
    }

    private TurboAutoAdvYDao.InformationBlock createAdvBlock(boolean mediaInDesign, String pageId) {
        log.info("createAdvBlock: {}, {}", mediaInDesign, pageId);
        Map<AdvertisingPlacement, String> answer = new EnumMap<>(AdvertisingPlacement.class);

        BlockAttributes blockAttributes = createBlock(mediaInDesign, pageId, DSP_BLOCK_SINGLE, List.of("above-header"));
        ResponseCreate responseCreate = advertisingIntegrationService.create(new AdvBlock(blockAttributes));
        answer.put(AdvertisingPlacement.TOP, responseCreate.getData().getId());
        log.info("createAdvBlock: above-header block create response: {}", responseCreate);

        blockAttributes = createBlock(mediaInDesign, pageId,  DSP_BLOCK_SINGLE, List.of("floor-ad"));
        responseCreate = advertisingIntegrationService.create(new AdvBlock(blockAttributes));
        answer.put(AdvertisingPlacement.STICKY, responseCreate.getData().getId());
        log.info("createAdvBlock: floor-ad block create response: {}", responseCreate);

        blockAttributes = createBlock(mediaInDesign, pageId,
                 DSP_BLOCK_MULTI_FORMAT, List.of("recommendation-feed"));
        responseCreate = advertisingIntegrationService.create(new AdvBlock(blockAttributes));
        answer.put(AdvertisingPlacement.TAPE, responseCreate.getData().getId());
        log.info("createAdvBlock: recommendation-feed block create response: {}", responseCreate);

        blockAttributes = createBlock(mediaInDesign, pageId,
                DSP_BLOCK_MULTI_FORMAT, List.of("in-content"));
        responseCreate = advertisingIntegrationService.create(new AdvBlock(blockAttributes));
        answer.put(AdvertisingPlacement.CONTENT, responseCreate.getData().getId());
        log.info("createAdvBlock: in-content block create response: {}", responseCreate);

        return new TurboAutoAdvYDao.InformationBlock(answer);
    }

    private void updateBlock(boolean mediaInDesign, TurboAutoAdvYDao.InformationBlock informationBlock) {
        BlockAttributes blockAttributes = updateBlock(mediaInDesign,  DSP_BLOCK_SINGLE, List.of("above-header"));
        AdvUpdateBlock advUpdateBlock = new AdvUpdateBlock(informationBlock.getBlocks().get(AdvertisingPlacement.TOP), blockAttributes);
        update(advUpdateBlock);

        blockAttributes = updateBlock(mediaInDesign,  DSP_BLOCK_SINGLE, List.of("floor-ad"));
        advUpdateBlock = new AdvUpdateBlock(informationBlock.getBlocks().get(AdvertisingPlacement.STICKY), blockAttributes);
        update(advUpdateBlock);

        blockAttributes = updateBlock(mediaInDesign,
                 DSP_BLOCK_MULTI_FORMAT, List.of("recommendation-feed"));
        advUpdateBlock = new AdvUpdateBlock(informationBlock.getBlocks().get(AdvertisingPlacement.TAPE), blockAttributes);
        update(advUpdateBlock);
        blockAttributes = updateBlock(mediaInDesign,
                 DSP_BLOCK_MULTI_FORMAT, List.of("in-content"));
        advUpdateBlock = new AdvUpdateBlock(informationBlock.getBlocks().get(AdvertisingPlacement.CONTENT), blockAttributes);
        update(advUpdateBlock);

    }

    private void update(AdvUpdateBlock advUpdateBlock) {
        final ResponseCreate update = advertisingIntegrationService.update(advUpdateBlock);
        if (update.getErrors() != null && !update.getErrors().isEmpty()) {
            String errorMessages = JsonMapping.writeValueAsString(update.getErrors());
            throw new WebmasterException(errorMessages,
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(TurboAutoAdvService.class, errorMessages), null);
        }
    }

    private String getId(IAdvRequest request) {
        String answer;
        log.info("getId: {}", request);
        ResponseFind response = advertisingIntegrationService.find(request);
        log.info("getId: find response: {}", response);
        if (response.getErrors() != null ||
                response.getData() == null ||
                response.getData().isEmpty()) {
            log.info("getId: entity does not exist, will create");
            final ResponseCreate responseCreate = advertisingIntegrationService.create(request);
            log.info("getId: create response: {}", responseCreate);
            answer = responseCreate.getData().getId();
        } else {
            answer = response.getData().get(0).getId();
        }

        log.info("getId: result: {}", answer);

        return answer;
    }
}
