package com.caliverse.admin.Indicators.Indicatorsservice.aggregationservice;

import com.caliverse.admin.Indicators.Indicatordomain.IndicatorsLog;
import com.caliverse.admin.Indicators.Indicatorsservice.base.IndicatorsLogLoadServiceBase;
import com.caliverse.admin.global.common.constants.AdminConstants;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class IndicatorsRetentionLoadService extends IndicatorsLogLoadServiceBase {
    public IndicatorsRetentionLoadService(
            @Qualifier("mongoIndicatorTemplate") MongoTemplate mongoTemplate
    ) {
        super(mongoTemplate);
    }

    @Override
    public <T extends IndicatorsLog> List<T> getIndicatorsLogData(String startTime, String endTime, Class<T> clazz) {
        LookupOperation lookupOperation = LookupOperation.newLookup()
                .from("userCreate")
                .localField("userGuid")
                .foreignField("userGuid")
                .as("userInfo");

        UnwindOperation unwindOperation = Aggregation.unwind("userInfo");

        Criteria criteria = makeCriteria(startTime, endTime, "userInfo.logDay");

        AddFieldsOperation addFieldsOp = Aggregation.addFields()
                .addField("createDate")
                .withValue(DateOperators.dateFromString("$userInfo.logDay"))
                .addField("loginDate")
                .withValue(DateOperators.dateFromString("$logDay"))
                .build();

        AggregationOperation addFieldsOp2 = context -> new Document("$addFields",
                new Document("daysSinceCreate",
                        new Document("$dateDiff", new Document()
                                .append("startDate", "$createDate")
                                .append("endDate", "$loginDate")
                                .append("unit", "day")
                        )
                )
        );

        MatchOperation matchOp = Aggregation.match(
                Criteria.where("daysSinceCreate").gte(1).lte(30)
        );

        AggregationOperation groupOp = context -> new Document("$group",
                new Document("_id", new Document("cohortDate", "$userInfo.logDay")
                        .append("userGuid", "$userGuid"))
                        .append("loginDays", new Document("$addToSet", "$daysSinceCreate"))
        );

        AggregationOperation groupOp2 = context -> new Document("$group",
                new Document("_id", "$_id.cohortDate")
                        .append("totalUsers", new Document("$sum", 1))
                        .append("d1_retained", new Document("$sum",
                                new Document("$cond", List.of(
                                        new Document("$in", List.of(1, "$loginDays")),
                                        1,
                                        0
                                ))
                        ))
                        .append("d7_retention_users", new Document("$sum",
                                new Document("$cond", List.of(
                                        new Document("$gt", List.of(
                                                new Document("$size", new Document("$filter", new Document()
                                                        .append("input", "$loginDays")
                                                        .append("cond", new Document("$and", List.of(
                                                                new Document("$gte", List.of("$$this", 1)),
                                                                new Document("$lte", List.of("$$this", 7)))
                                                        ))
                                                )),
                                                0
                                        )),
                                        1,
                                        0
                                ))
                        ))
                        .append("d30_retention_users", new Document("$sum",
                                new Document("$cond", List.of(
                                        new Document("$gt", List.of(
                                                new Document("$size", new Document("$filter", new Document()
                                                        .append("input", "$loginDays")
                                                        .append("cond", new Document("$and", List.of(
                                                                new Document("$gte", List.of("$$this", 1)),
                                                                new Document("$lte", List.of("$$this", 30)))
                                                        ))
                                                )),
                                                0
                                        )),
                                        1,
                                        0
                                ))
                        ))
        );

        AggregationOperation lookupOp = context -> new Document("$lookup",
                new Document("from", "userCreate")
                        .append("let", new Document("cohortDate", "$_id"))
                        .append("pipeline", List.of(
                                new Document("$match", new Document("$expr",
                                        new Document("$eq", List.of("$logDay", "$$cohortDate"))
                                )),
                                new Document("$count", "totalCreated")
                        ))
                        .append("as", "cohortInfo")
        );

        UnwindOperation unwindOp = Aggregation.unwind("cohortInfo", true);

        AggregationOperation projectOp = context -> new Document("$project", new Document()
                .append("_id", 0)
                .append("logDay", "$_id")
                .append("totalCreate", new Document("$ifNull", List.of("$cohortInfo.totalCreated", 0)))
                .append("totalActiveUsers", "$totalUsers")
                .append("d1_users", 1)
                .append("d7_users", 1)
                .append("d30_users", 1)
                .append("d1_rate", new Document("$cond", List.of(
                        new Document("$gt", List.of("$cohortInfo.totalCreated", 0)),
                        new Document("$multiply", List.of(
                                new Document("$divide", List.of("$d1_retained", "$cohortInfo.totalCreated")),
                                100
                        )),
                        0
                )))
                .append("d7_rate", new Document("$cond", List.of(
                        new Document("$gt", List.of("$cohortInfo.totalCreated", 0)),
                        new Document("$multiply", List.of(
                                new Document("$divide", List.of("$d7_retention_users", "$cohortInfo.totalCreated")),
                                100
                        )),
                        0
                )))
                .append("d30_rate", new Document("$cond", List.of(
                        new Document("$gt", List.of("$cohortInfo.totalCreated", 0)),
                        new Document("$multiply", List.of(
                                new Document("$divide", List.of("$d30_retention_users", "$cohortInfo.totalCreated")),
                                100
                        )),
                        0
                )))
        );

        List<AggregationOperation> operations = List.of(
                lookupOperation,
                unwindOperation,
                Aggregation.match(criteria),
                addFieldsOp,
                addFieldsOp2,
                matchOp,
                groupOp,
                groupOp2,
                lookupOp,
                unwindOp,
                projectOp,
                Aggregation.sort(Sort.Direction.ASC, AdminConstants.MONGO_DB_KEY_LOGDAY)
        );

        Aggregation aggregation = Aggregation.newAggregation(operations);

        return mongoTemplate.aggregate(
                aggregation.withOptions(AggregationOptions.builder().allowDiskUse(true).build())
                , AdminConstants.MONGO_DB_COLLECTION_LOGIN
                , clazz
        ).getMappedResults();
    }
}
