package ru.yandex.canvas.service;

import java.util.List;

import com.google.common.base.MoreObjects;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

import ru.yandex.canvas.model.CreativeDocumentBatch;

import static org.springframework.data.mongodb.core.aggregation.Aggregation.group;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.match;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.project;


/**
 * @author mavlyutov
 */
public class MongoMonitorService {

    private final MongoOperations mongoOperation;
    private final String authToken;

    public MongoMonitorService(final MongoOperations mongoOperation, String authToken) {
        this.mongoOperation = mongoOperation;
        this.authToken = authToken;
    }

    private void ensureAllowed(String authToken) {
        if (!this.authToken.equals(authToken)) {
            throw new MongoMonitorAuthException();
        }
    }

    public CountRequestResult ideasBatches(String authToken) {

        ensureAllowed(authToken);

        long ideasBatches =
                mongoOperation.count(Query.query(Criteria.where("ideaId").exists(true)), CreativeDocumentBatch.class);

        return new CountRequestResult(null, ideasBatches);
    }

    public CountRequestResult ideasClients(String authToken) {

        ensureAllowed(authToken);

        TypedAggregation<CreativeDocumentBatch> aggregation = newAggregation(CreativeDocumentBatch.class,
                match(Criteria.where("ideaId").exists(true).andOperator(Criteria.where("ideaId").ne(null))),
                project("clientId"),
                group("$clientId"),
                group().count().as("count"));

        CountRequestResult clients = mongoOperation.aggregate(aggregation,
                CountRequestResult.class).getUniqueMappedResult();

        return MoreObjects.firstNonNull(clients, new CountRequestResult(null, 0));
    }

    public List<CreativeGroup> totalCreativesPerClient(String authToken) {

        ensureAllowed(authToken);

        TypedAggregation agg = newAggregation(CreativeDocumentBatch.class,
                project("clientId").and("items").size().as("numberOfCreatives"),
                group("clientId").sum("numberOfCreatives").as("totalCreatives"),
                project("totalCreatives").and("_id").as("clientId")
                        .andExpression("totalCreatives - (totalCreatives % 10)").as("creativeGroup"),
                group("creativeGroup").count().as("clients")
        );

        return mongoOperation.aggregate(agg, CreativeGroup.class).getMappedResults();
    }

    @Document
    public static class CountRequestResult {
        @Id
        private String id;
        private long count = 0;

        public CountRequestResult(String id, long count) {
            this.id = id;
            this.count = count;
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public long getCount() {
            return count;
        }

        public void setCount(long count) {
            this.count = count;
        }

    }

    @Document
    public static class CreativeGroup {
        @Id
        private String id;
        private long clients = 0;

        public long getClients() {
            return clients;
        }

        public void setClients(long clients) {
            this.clients = clients;
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

    }

    @ResponseStatus(value = HttpStatus.FORBIDDEN)
    public static class MongoMonitorAuthException extends RuntimeException {
        MongoMonitorAuthException() {
        }
    }
}
