package ru.yandex.canvas.service.multitype.operation;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;

import javax.annotation.ParametersAreNonnullByDefault;

import com.mongodb.client.result.UpdateResult;
import one.util.streamex.StreamEx;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import ru.yandex.canvas.model.CreativeWithAdminRejectReason;
import ru.yandex.canvas.repository.html5.BatchesRepository;
import ru.yandex.canvas.service.CreativesService;
import ru.yandex.canvas.service.multitype.CreativeType;
import ru.yandex.canvas.service.multitype.MultiCreativeTypeBaseOperation;
import ru.yandex.canvas.service.multitype.MultiCreativeTypeOperation;
import ru.yandex.canvas.service.multitype.OnCreativeOperationResult;
import ru.yandex.canvas.service.multitype.OnCreativeServiceProvider;
import ru.yandex.canvas.service.multitype.SingleCreativeTypeBaseOperation;
import ru.yandex.canvas.service.multitype.SingleCreativeTypeOperation;
import ru.yandex.canvas.service.multitype.SingleCreativeTypeOperationProvider;
import ru.yandex.canvas.service.multitype.request.AdminRejectOperationMultiTypeRequest;
import ru.yandex.canvas.service.multitype.singletype.request.AdminRejectOperationSingleTypeRequest;

import static ru.yandex.canvas.service.multitype.CreativeType.AD_BUILDER;
import static ru.yandex.canvas.service.multitype.CreativeType.HTML5;
import static ru.yandex.canvas.service.multitype.OnCreativeOperationResult.error;
import static ru.yandex.canvas.service.multitype.OnCreativeOperationResult.ok;

/**
 * Операция для запрета использования креатива.
 */
@Lazy
@Component
@ParametersAreNonnullByDefault
public class AdminRejectOperation
        extends MultiCreativeTypeBaseOperation<AdminRejectOperationMultiTypeRequest>
        implements MultiCreativeTypeOperation<AdminRejectOperationMultiTypeRequest> {
    private final BatchesRepository html5BatchesRepository;
    private final CreativesService creativesService;

    AdminRejectOperation(OnCreativeServiceProvider onCreativeServiceProvider,
                         BatchesRepository html5BatchesRepository,
                         CreativesService creativesService) {
        super(onCreativeServiceProvider);
        this.html5BatchesRepository = html5BatchesRepository;
        this.creativesService = creativesService;
    }

    @SuppressWarnings("unused")
    @SingleCreativeTypeOperationProvider(HTML5)
    public SingleCreativeTypeOperation adminRejectHtml5CreativesOperation() {
        return new AdminRejectInnerOperation<>(HTML5,
                html5BatchesRepository::updateAdminRejectReason,
                ids -> StreamEx.of(html5BatchesRepository.getBatchesByCreativeIdsIncludeArchived(List.copyOf(ids)))
                        .flatMap(batch -> batch.getCreatives().stream())
                        .filter(creative -> ids.contains(creative.getId()))
                        .toList());
    }

    @SuppressWarnings("unused")
    @SingleCreativeTypeOperationProvider(AD_BUILDER)
    public SingleCreativeTypeOperation adminRejectAdBuilderCreativesOperation() {

        return new AdminRejectInnerOperation<>(AD_BUILDER,
                creativesService::updateAdminRejectReason,
                creativesService::getCreatives);
    }

    private static class AdminRejectInnerOperation<T extends CreativeWithAdminRejectReason>
            extends SingleCreativeTypeBaseOperation<T, AdminRejectOperationSingleTypeRequest> {
        private final BiFunction<Collection<Long>, String, UpdateResult> applyMethod;
        private final Function<Set<Long>, Collection<T>> checkMethod;
        private final CreativeType worksOn;

        private AdminRejectInnerOperation(CreativeType worksOn,
                                          BiFunction<Collection<Long>, String, UpdateResult> applyMethod,
                                          Function<Set<Long>, Collection<T>> checkMethod) {
            this.worksOn = worksOn;
            this.applyMethod = applyMethod;
            this.checkMethod = checkMethod;
        }

        @Override
        public CreativeType worksOn() {
            return worksOn;
        }

        @Override
        public void run(AdminRejectOperationSingleTypeRequest request, Map<Long, OnCreativeOperationResult> result) {
            List<Long> creativeIds = request.getCreativeIds();
            if (creativeIds.isEmpty()) {
                return;
            }

            Set<Long> uniqueCreativeIds = Set.copyOf(creativeIds);
            String reason = request.getReason();
            UpdateResult updateResult = applyMethod.apply(uniqueCreativeIds, reason);

            if (updateResult.getModifiedCount() == uniqueCreativeIds.size()) {
                uniqueCreativeIds.forEach(id -> result.put(id, ok()));
            } else {
                checkMethod.apply(uniqueCreativeIds)
                        .forEach(creative -> result.put(creative.fetchCreativeId(),
                                Objects.equals(reason, creative.getAdminRejectReason())
                                        ? ok()
                                        : error("Failed to adminReject creative " + creative.fetchCreativeId())));
            }
        }
    }
}
