package ru.yandex.direct.web.entity.excel.service;

import java.util.Objects;

import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;

import one.util.streamex.StreamEx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import ru.yandex.direct.core.entity.adgroup.container.InternalAdGroupAddOrUpdateItem;
import ru.yandex.direct.core.entity.adgroup.model.InternalAdGroup;
import ru.yandex.direct.core.entity.adgroupadditionaltargeting.model.AdGroupAdditionalTargeting;
import ru.yandex.direct.core.entity.banner.container.InternalBannerAddOrUpdateItem;
import ru.yandex.direct.core.entity.banner.model.TemplateVariable;
import ru.yandex.direct.core.entity.retargeting.model.RetargetingConditionBase;
import ru.yandex.direct.excel.processing.model.internalad.ExcelFetchedData;
import ru.yandex.direct.excel.processing.service.internalad.InternalAdExcelService;
import ru.yandex.direct.grid.processing.service.validation.presentation.SkipByDefaultMappingPathNodeConverter;
import ru.yandex.direct.result.Result;
import ru.yandex.direct.validation.builder.ItemValidationBuilder;
import ru.yandex.direct.validation.builder.Validator;
import ru.yandex.direct.validation.builder.When;
import ru.yandex.direct.validation.defect.CommonDefects;
import ru.yandex.direct.validation.result.DefaultPathNodeConverterProvider;
import ru.yandex.direct.validation.result.Defect;
import ru.yandex.direct.validation.result.Path;
import ru.yandex.direct.validation.result.PathNodeConverterProvider;
import ru.yandex.direct.validation.result.ValidationResult;
import ru.yandex.direct.web.entity.excel.model.ExcelFileKey;
import ru.yandex.direct.web.entity.excel.model.UploadedImageInfo;
import ru.yandex.direct.web.entity.excel.model.internalad.InternalAdExportMode;
import ru.yandex.direct.web.entity.excel.model.internalad.InternalAdExportRequest;
import ru.yandex.direct.web.entity.excel.model.internalad.InternalAdImportRequest;
import ru.yandex.direct.web.entity.excel.model.internalad.InternalAdTemplateExportRequest;
import ru.yandex.direct.web.validation.kernel.ValidationResultConversionService;
import ru.yandex.direct.web.validation.model.ValidationResponse;
import ru.yandex.direct.web.validation.model.WebValidationResult;

import static ru.yandex.direct.validation.constraint.CollectionConstraints.maxSetSize;
import static ru.yandex.direct.validation.constraint.CollectionConstraints.notEmptyCollection;
import static ru.yandex.direct.validation.constraint.CollectionConstraints.setSize;
import static ru.yandex.direct.validation.constraint.CommonConstraints.eachInSetNotNull;
import static ru.yandex.direct.validation.constraint.CommonConstraints.eachInSetValidId;
import static ru.yandex.direct.validation.constraint.CommonConstraints.isNull;
import static ru.yandex.direct.validation.constraint.CommonConstraints.notNull;
import static ru.yandex.direct.validation.constraint.CommonConstraints.validId;
import static ru.yandex.direct.validation.constraint.StringConstraints.isPositiveWholeNumber;
import static ru.yandex.direct.validation.result.PathHelper.path;
import static ru.yandex.direct.web.entity.excel.service.validation.ExcelConstraints.fileContentTypeIsSupported;
import static ru.yandex.direct.web.entity.excel.service.validation.ExcelConstraints.fileSizeIsValid;
import static ru.yandex.direct.web.entity.excel.service.validation.InternalAdPathConverters.ADDITIONAL_TARGETINGS_PATH_CONVERTER;
import static ru.yandex.direct.web.entity.excel.service.validation.InternalAdPathConverters.ADS_IMAGES_PATH_CONVERTER;
import static ru.yandex.direct.web.entity.excel.service.validation.InternalAdPathConverters.EXCEL_FETCHED_DATA_PATH_CONVERTER;
import static ru.yandex.direct.web.entity.excel.service.validation.InternalAdPathConverters.EXCEL_FILE_INFO_PATH_CONVERTER;
import static ru.yandex.direct.web.entity.excel.service.validation.InternalAdPathConverters.IMPORT_EXCEL_FILE_CONTENT_TYPE_PATH;
import static ru.yandex.direct.web.entity.excel.service.validation.InternalAdPathConverters.IMPORT_EXCEL_FILE_SIZE_PATH;
import static ru.yandex.direct.web.entity.excel.service.validation.InternalAdPathConverters.INTERNAL_AD_EXPORT_REQUEST_PATH_CONVERTER;
import static ru.yandex.direct.web.entity.excel.service.validation.InternalAdPathConverters.INTERNAL_AD_GROUP_ADD_OR_UPDATE_ITEM_PATH_CONVERTER;
import static ru.yandex.direct.web.entity.excel.service.validation.InternalAdPathConverters.INTERNAL_AD_GROUP_PATH_CONVERTER;
import static ru.yandex.direct.web.entity.excel.service.validation.InternalAdPathConverters.INTERNAL_AD_IMPORT_REQUEST_PATH_CONVERTER;
import static ru.yandex.direct.web.entity.excel.service.validation.InternalAdPathConverters.INTERNAL_BANNER_PATH_CONVERTER;
import static ru.yandex.direct.web.entity.excel.service.validation.InternalAdPathConverters.RETARGETING_CONDITION_PATH_CONVERTER;
import static ru.yandex.direct.web.entity.excel.service.validation.InternalAdPathConverters.toImportExcelFilePath;

@Service
@ParametersAreNonnullByDefault
public class InternalAdValidationService {

    static final int MAX_IDS_PER_REQUEST = InternalAdExcelService.MAX_AD_GROUP_COUNT;
    static final int MAX_FILE_SIZE = 1024 * 1024 * 10; //10МБ

    private final ValidationResultConversionService validationResultConversionService;
    private final PathNodeConverterProvider pathNodeConverterProvider;

    @Autowired
    public InternalAdValidationService(ValidationResultConversionService validationResultConversionService) {
        this.validationResultConversionService = validationResultConversionService;
        this.pathNodeConverterProvider = DefaultPathNodeConverterProvider.builder()
                .register(InternalAdExportRequest.class, INTERNAL_AD_EXPORT_REQUEST_PATH_CONVERTER)
                .register(ExcelFetchedData.class, EXCEL_FETCHED_DATA_PATH_CONVERTER)

                .register(InternalAdImportRequest.class, INTERNAL_AD_IMPORT_REQUEST_PATH_CONVERTER)
                .register(ExcelFileKey.class, EXCEL_FILE_INFO_PATH_CONVERTER)
                .register(UploadedImageInfo.class, ADS_IMAGES_PATH_CONVERTER)

                .register(InternalAdGroupAddOrUpdateItem.class, INTERNAL_AD_GROUP_ADD_OR_UPDATE_ITEM_PATH_CONVERTER)
                .register(InternalAdGroup.class, INTERNAL_AD_GROUP_PATH_CONVERTER)
                .register(AdGroupAdditionalTargeting.class, ADDITIONAL_TARGETINGS_PATH_CONVERTER)
                .register(RetargetingConditionBase.class, RETARGETING_CONDITION_PATH_CONVERTER)

                .register(InternalBannerAddOrUpdateItem.class, INTERNAL_BANNER_PATH_CONVERTER)
                .register(TemplateVariable.class, SkipByDefaultMappingPathNodeConverter.emptyConverter())
                .build();
    }

    public ValidationResponse buildValidationResponse(Result<?> massResult) {
        return validationResultConversionService
                .buildValidationResponse(massResult.getValidationResult(), pathNodeConverterProvider);
    }

    public WebValidationResult buildWebValidationResult(@Nullable ValidationResult<?, Defect> vr) {
        return buildWebValidationResult(vr, path());
    }

    public WebValidationResult buildWebValidationResult(@Nullable ValidationResult<?, Defect> vr,
                                                        Path path) {
        return validationResultConversionService.buildWebValidationResult(vr, path, pathNodeConverterProvider);
    }

    private static final Validator<ExcelFileKey, Defect> EXCEL_FILE_KEY_VALIDATOR =
            excelFileKey -> {
                ItemValidationBuilder<ExcelFileKey, Defect> vb = ItemValidationBuilder.of(excelFileKey);

                vb.item(excelFileKey.getFileName(), ExcelFileKey.FILE_NAME)
                        .check(notNull());
                vb.item(excelFileKey.getMdsGroupId(), ExcelFileKey.MDS_GROUP)
                        .check(notNull())
                        .check(isPositiveWholeNumber());

                return vb.getResult();
            };

    private static final Validator<UploadedImageInfo, Defect> UPLOADED_IMAGE_INFO_VALIDATOR =
            uploadedImageInfo -> {
                ItemValidationBuilder<UploadedImageInfo, Defect> vb = ItemValidationBuilder.of(uploadedImageInfo);

                vb.item(uploadedImageInfo.getFileName(), UploadedImageInfo.FILE_NAME)
                        .check(notNull());
                vb.item(uploadedImageInfo.getImageHash(), UploadedImageInfo.IMAGE_HASH)
                        .check(notNull());

                return vb.getResult();
            };

    static ValidationResult<InternalAdExportRequest, Defect> validateExportRequest(InternalAdExportRequest request) {
        ItemValidationBuilder<InternalAdExportRequest, Defect> vb = ItemValidationBuilder.of(request);

        vb.item(request.getExportMode(), InternalAdExportRequest.EXPORT_MODE)
                .check(notNull());

        vb.check(InternalAdValidationService::hasOnlyOneFilterField);

        vb.item(request.getCampaignIds(), InternalAdExportRequest.CAMPAIGN_IDS)
                //пока ожидается одна кампания. Сделали списком на будущее
                .check(setSize(1, 1))
                .check(eachInSetNotNull(), When.isValid())
                .check(eachInSetValidId(), When.isValid());

        vb.item(request.getAdGroupIds(), InternalAdExportRequest.AD_GROUP_IDS)
                .check(notEmptyCollection())
                .check(maxSetSize(MAX_IDS_PER_REQUEST), When.isValid())
                .check(eachInSetNotNull(), When.isValid())
                .check(eachInSetValidId(), When.isValid());

        vb.item(request.getAdIds(), InternalAdExportRequest.AD_IDS)
                .check(isNull(), When.isTrue(request.getExportMode() == InternalAdExportMode.ONLY_AD_GROUPS))
                .check(notEmptyCollection())
                .check(maxSetSize(MAX_IDS_PER_REQUEST), When.isValid())
                .check(eachInSetNotNull(), When.isValid())
                .check(eachInSetValidId(), When.isValid());

        return vb.getResult();
    }

    static ValidationResult<MultipartFile, Defect> validateImportFile(MultipartFile excelFile) {
        ItemValidationBuilder<MultipartFile, Defect> vb = ItemValidationBuilder.of(excelFile);

        // добавляем к пути префикс тут, т.к. у MultipartFile может быть много реализаций,
        // а по интерфейсу pathConverter не умеет работать
        vb.item(excelFile.getContentType(), toImportExcelFilePath(IMPORT_EXCEL_FILE_CONTENT_TYPE_PATH))
                .check(notNull())
                .check(fileContentTypeIsSupported());
        vb.item(excelFile.getSize(), toImportExcelFilePath(IMPORT_EXCEL_FILE_SIZE_PATH))
                .check(fileSizeIsValid(MAX_FILE_SIZE));

        return vb.getResult();
    }

    static ValidationResult<InternalAdImportRequest, Defect> validateImportRequest(InternalAdImportRequest request) {
        ItemValidationBuilder<InternalAdImportRequest, Defect> vb = ItemValidationBuilder.of(request);

        vb.item(request.getExcelFileKey(), InternalAdImportRequest.EXCEL_FILE_KEY)
                .check(notNull())
                .checkBy(EXCEL_FILE_KEY_VALIDATOR, When.isValid());

        vb.list(request.getAdsImages(), InternalAdImportRequest.ADS_IMAGES)
                .check(notNull())
                .checkEach(notNull())
                .checkEachBy(UPLOADED_IMAGE_INFO_VALIDATOR, When.isValid());

        vb.item(request.getImportMode(), InternalAdImportRequest.IMPORT_MODE)
                .check(notNull());

        vb.item(request.getOnlyValidation(), InternalAdImportRequest.ONLY_VALIDATION)
                .check(notNull());

        return vb.getResult();
    }

    static ValidationResult<InternalAdTemplateExportRequest, Defect> validateTemplateExportRequest(
            InternalAdTemplateExportRequest request) {
        ItemValidationBuilder<InternalAdTemplateExportRequest, Defect> vb = ItemValidationBuilder.of(request);

        vb.item(request.getCampaignId(), InternalAdTemplateExportRequest.CAMPAIGN_ID)
                .check(notNull())
                .check(validId());

        vb.item(request.getPlaceId(), InternalAdTemplateExportRequest.PLACE_ID)
                .check(notNull())
                .check(validId());

        vb.item(request.getTemplateId(), InternalAdTemplateExportRequest.TEMPLATE_ID)
                .check(notNull())
                .check(validId());

        return vb.getResult();
    }

    @Nullable
    private static Defect<Void> hasOnlyOneFilterField(InternalAdExportRequest request) {
        long nonNullFieldsCount = StreamEx.of(request.getCampaignIds(), request.getAdGroupIds(), request.getAdIds())
                .filter(Objects::nonNull)
                .count();

        return nonNullFieldsCount == 1 ? null : CommonDefects.inconsistentState();
    }
}
