package ru.yandex.direct.api.v5.entity.smartadtargets.validation;

import java.util.List;
import java.util.Objects;
import java.util.Set;

import javax.annotation.ParametersAreNonnullByDefault;

import com.yandex.direct.api.v5.general.AdTargetsSelectionCriteria;
import com.yandex.direct.api.v5.general.IdsCriteria;
import com.yandex.direct.api.v5.smartadtargets.AddRequest;
import com.yandex.direct.api.v5.smartadtargets.DeleteRequest;
import com.yandex.direct.api.v5.smartadtargets.GetRequest;
import com.yandex.direct.api.v5.smartadtargets.ResumeRequest;
import com.yandex.direct.api.v5.smartadtargets.SmartAdTargetAddItem;
import com.yandex.direct.api.v5.smartadtargets.SuspendRequest;
import com.yandex.direct.api.v5.smartadtargets.UpdateRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import ru.yandex.direct.api.v5.validation.DefectType;
import ru.yandex.direct.core.entity.adgroup.model.AdGroup;
import ru.yandex.direct.core.entity.adgroup.model.AdGroupType;
import ru.yandex.direct.core.entity.adgroup.service.AdGroupService;
import ru.yandex.direct.dbutil.model.ClientId;
import ru.yandex.direct.validation.builder.ItemValidationBuilder;
import ru.yandex.direct.validation.builder.ListValidationBuilder;
import ru.yandex.direct.validation.builder.When;
import ru.yandex.direct.validation.result.ValidationResult;

import static ru.yandex.direct.api.v5.entity.smartadtargets.SmartAdTargetsDefectTypes.inconsistentAdGroupType;
import static ru.yandex.direct.api.v5.entity.smartadtargets.SmartAdTargetsDefectTypes.maxElementsPerRequest;
import static ru.yandex.direct.api.v5.entity.smartadtargets.SmartAdTargetsDefectTypes.maxIdsCountPerRequest;
import static ru.yandex.direct.api.v5.validation.constraints.Constraints.inSet;
import static ru.yandex.direct.api.v5.validation.constraints.Constraints.maxListSize;
import static ru.yandex.direct.api.v5.validation.constraints.Constraints.notNull;
import static ru.yandex.direct.utils.FunctionalUtils.filterAndMapToSet;
import static ru.yandex.direct.utils.FunctionalUtils.mapList;

@ParametersAreNonnullByDefault
@Service
public class SmartAdTargetValidationService {
    private static final int MAX_ELEMENTS_PER_REQUEST = 1_000;
    private static final int MAX_CAMPAIGN_IDS_PER_REQUEST = 2;
    private static final int MAX_AD_GROUP_IDS_PER_REQUEST = 1_000;
    private static final int MAX_FILTER_IDS_PER_REQUEST = 10_000;

    private final AdGroupService adGroupService;

    @Autowired
    public SmartAdTargetValidationService(AdGroupService adGroupService) {
        this.adGroupService = adGroupService;
    }

    public ValidationResult<AddRequest, DefectType> validateAddRequest(ClientId clientId, AddRequest addRequest) {
        ItemValidationBuilder<AddRequest, DefectType> vb = ItemValidationBuilder.of(addRequest);
        ListValidationBuilder<SmartAdTargetAddItem, DefectType> lvb =
                vb.list(addRequest.getSmartAdTargets(), AddRequest.PropInfo.SMART_AD_TARGETS.propertyName);
        lvb.checkEach(notNull());
        lvb.check(maxListSize(MAX_ELEMENTS_PER_REQUEST), maxElementsPerRequest(MAX_ELEMENTS_PER_REQUEST));
        // Если уже есть ошибки, то запрос вообще не валидный и дорогую валидацию с запросами в базу можно не делать.
        if (vb.getResult().hasAnyErrors()) {
            return vb.getResult();
        }
        // Валидируем тип группы здесь и сейчас, т.к. если у группы не подходящий тип, то в базе для неё не найдётся фид
        // и запрос не получится правильно сконвертировать в ядровую модель - для конвертации нужен фид.
        List<Long> adGroupIds = mapList(addRequest.getSmartAdTargets(), SmartAdTargetAddItem::getAdGroupId);
        List<AdGroup> adGroups = adGroupService.getAdGroups(clientId, adGroupIds);
        Set<Long> smartGroupIds = filterAndMapToSet(adGroups,
                ag -> Objects.equals(ag.getType(), AdGroupType.PERFORMANCE),
                AdGroup::getId);
        lvb.checkEachBy(tg -> validateAddItem(tg, smartGroupIds), When.isValid());
        return vb.getResult();
    }

    private ValidationResult<SmartAdTargetAddItem, DefectType> validateAddItem(SmartAdTargetAddItem tg,
                                                                               Set<Long> smartGroupIds) {
        ItemValidationBuilder<SmartAdTargetAddItem, DefectType> vb = ItemValidationBuilder.of(tg);
        vb.item(tg.getAdGroupId(), "AdGroupId")
                .check(inSet(smartGroupIds), inconsistentAdGroupType());
        return vb.getResult();
    }

    public ValidationResult<UpdateRequest, DefectType> validateUpdateRequest(UpdateRequest updateRequest) {
        ItemValidationBuilder<UpdateRequest, DefectType> vb = ItemValidationBuilder.of(updateRequest);
        vb.list(updateRequest.getSmartAdTargets(), UpdateRequest.PropInfo.SMART_AD_TARGETS.propertyName)
                .check(maxListSize(MAX_ELEMENTS_PER_REQUEST),
                        maxElementsPerRequest(MAX_ELEMENTS_PER_REQUEST));
        return vb.getResult();
    }

    public ValidationResult<DeleteRequest, DefectType> validateDeleteRequest(DeleteRequest deleteRequest) {
        ItemValidationBuilder<DeleteRequest, DefectType> vb = ItemValidationBuilder.of(deleteRequest);
        validateSelectionCriteria(vb, deleteRequest.getSelectionCriteria());
        return vb.getResult();
    }

    public ValidationResult<SuspendRequest, DefectType> validateSuspendRequest(SuspendRequest suspendRequest) {
        ItemValidationBuilder<SuspendRequest, DefectType> vb = ItemValidationBuilder.of(suspendRequest);
        validateSelectionCriteria(vb, suspendRequest.getSelectionCriteria());
        return vb.getResult();
    }

    public ValidationResult<ResumeRequest, DefectType> validateResumeRequest(ResumeRequest resumeRequest) {
        ItemValidationBuilder<ResumeRequest, DefectType> vb = ItemValidationBuilder.of(resumeRequest);
        validateSelectionCriteria(vb, resumeRequest.getSelectionCriteria());
        return vb.getResult();
    }

    // Документация по которой построена валидация лежит здесь:
    // https://yandex.ru/dev/direct/doc/ref-v5/smartadtargets/get-docpage/#input
    public ValidationResult<GetRequest, DefectType> validateGetRequest(GetRequest getRequest) {
        ItemValidationBuilder<GetRequest, DefectType> vb = ItemValidationBuilder.of(getRequest);
        AdTargetsSelectionCriteria selectionCriteria = getRequest.getSelectionCriteria();

        ItemValidationBuilder<AdTargetsSelectionCriteria, DefectType> item =
                vb.item(selectionCriteria, "SelectionCriteria");
        validateIds(item, selectionCriteria.getIds(), "Ids", MAX_FILTER_IDS_PER_REQUEST);
        validateIds(item, selectionCriteria.getAdGroupIds(), "AdGroupIds", MAX_AD_GROUP_IDS_PER_REQUEST);
        validateIds(item, selectionCriteria.getCampaignIds(), "CampaignIds", MAX_CAMPAIGN_IDS_PER_REQUEST);
        return vb.getResult();
    }

    private void validateSelectionCriteria(ItemValidationBuilder<?, DefectType> vb, IdsCriteria selectionCriteria) {
        ItemValidationBuilder<IdsCriteria, DefectType> item = vb.item(selectionCriteria, "SelectionCriteria");
        validateIds(item, selectionCriteria.getIds(), "Ids", MAX_FILTER_IDS_PER_REQUEST);
    }

    private static void validateIds(ItemValidationBuilder<?, DefectType> item, List<Long> ids, String name, int max) {
        item.list(ids, name)
                .checkEach(notNull())
                .check(maxListSize(max), maxIdsCountPerRequest(max));
    }

}
