package ru.yandex.qe.dispenser.ws;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;

import io.swagger.annotations.Api;
import io.swagger.annotations.Authorization;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;

import ru.yandex.qe.dispenser.api.v1.DiSegmentation;
import ru.yandex.qe.dispenser.domain.CampaignResource;
import ru.yandex.qe.dispenser.domain.QuotaSpec;
import ru.yandex.qe.dispenser.domain.Resource;
import ru.yandex.qe.dispenser.domain.Segment;
import ru.yandex.qe.dispenser.domain.Segmentation;
import ru.yandex.qe.dispenser.domain.Service;
import ru.yandex.qe.dispenser.domain.dao.campaign.CampaignResourceDao;
import ru.yandex.qe.dispenser.domain.dao.resource.segmentation.ResourceSegmentationDao;
import ru.yandex.qe.dispenser.domain.dao.segment.SegmentDao;
import ru.yandex.qe.dispenser.domain.dao.segmentation.SegmentationReader;
import ru.yandex.qe.dispenser.domain.hierarchy.Hierarchy;
import ru.yandex.qe.dispenser.domain.index.LongIndexBase;
import ru.yandex.qe.dispenser.swagger.DispenserSecurityDefinition;
import ru.yandex.qe.dispenser.swagger.SwaggerTags;
import ru.yandex.qe.dispenser.ws.param.ResourceParam;
import ru.yandex.qe.dispenser.ws.reqbody.SegmentationBody;

import static ru.yandex.qe.dispenser.ws.ServiceResourceService.RESOURCE_KEY;
import static ru.yandex.qe.dispenser.ws.ServiceService.SERVICE_KEY;

@Path("/v1/services/{" + SERVICE_KEY + "}/resources/{" + RESOURCE_KEY + "}/segmentations")
@Produces(ServiceBase.APPLICATION_JSON_UTF_8)
@org.springframework.stereotype.Service("resourceSegmentation")
@Api(tags = {SwaggerTags.DISPENSER_API}, authorizations = {@Authorization(value = DispenserSecurityDefinition.AUTHORIZATION_SCHEME_NAME)})
public class ResourceSegmentationService extends ServiceBase {

    @Autowired
    private ResourceSegmentationDao resourceSegmentationDao;
    @Autowired
    private CampaignResourceDao campaignResourceDao;
    @Autowired
    private SegmentDao segmentDao;

    @GET
    @NotNull
    public List<DiSegmentation> getSegmentations(@PathParam(SERVICE_KEY) @NotNull final Service service,
                                                 @PathParam(RESOURCE_KEY) @NotNull final String resourceKey) {
        final ResourceParam resourceParam = new ResourceParam(service, resourceKey);
        final Resource resource = resourceParam.get();
        ServiceEndpointUtils.memoizeServiceForResource(resource);
        final Set<Segmentation> segmentations = Hierarchy.get().getResourceSegmentationReader().getSegmentations(resource);

        return segmentations.stream()
                .map(Segmentation::toView)
                .collect(Collectors.toList());
    }

    @PUT
    @Access(dispenserAdmin = true)
    @NotNull
    public List<DiSegmentation> setSegmentations(@PathParam(SERVICE_KEY) @NotNull final Service service,
                                                 @PathParam(RESOURCE_KEY) @NotNull final String resourceKey,
                                                 final Set<SegmentationBody> newSegmentations) {
        final ResourceParam resourceParam = new ResourceParam(service, resourceKey);
        final SegmentationReader segmentationReader = Hierarchy.get().getSegmentationReader();
        final Resource resource = resourceParam.get();
        ServiceEndpointUtils.memoizeServiceForResource(resource);

        final Collection<QuotaSpec> quotaSpecs = Hierarchy.get().getQuotaSpecReader().getByResources(Collections.singleton(resource)).values();

        if (!quotaSpecs.isEmpty()) {
            throw new IllegalArgumentException("Can't modify segmentations for resource with created quota spec");
        }

        final Set<Segmentation> segmentations = newSegmentations.stream()
                .map(body -> segmentationReader.read(new Segmentation.Key(body.getKey())))
                .collect(Collectors.toSet());

        validateCampaignResources(segmentations, resource);

        resourceSegmentationDao.setSegmentations(resource, segmentations);

        return segmentations.stream()
                .map(Segmentation::toView)
                .collect(Collectors.toList());
    }

    private void validateCampaignResources(@NotNull final Set<Segmentation> newSegmentations,
                                           @NotNull final Resource resource) {
        final List<CampaignResource> campaignResources = campaignResourceDao.getByResourceId(resource.getId());
        final Set<Long> newSegmentationIds = newSegmentations.stream()
                .map(LongIndexBase::getId).collect(Collectors.toSet());
        final Map<Long, Set<Segment>> segmentsBySegmentationId = segmentDao.getBySegmentationIds(newSegmentationIds);
        final Set<Long> segmentIds = segmentsBySegmentationId.values().stream().flatMap(s -> s.stream()
                .map(LongIndexBase::getId)).collect(Collectors.toSet());
        final boolean requiredSegmentationMissing = campaignResources.stream().anyMatch(cr -> {
            final boolean segmentationMissing = cr.getSettings().getSegmentations().stream()
                    .anyMatch(s -> !newSegmentationIds.contains(s.getId()));
            final boolean segmentMissing = cr.getSettings().getSegmentsBigOrders().stream()
                    .anyMatch(s -> s.getSegments().stream().anyMatch(id -> !segmentIds.contains(id)));
            return segmentationMissing || segmentMissing;
        });
        if (requiredSegmentationMissing) {
            throw new IllegalArgumentException("Some of the removed segmentations are referenced by one or more campaign settings.");
        }
    }

}
