package com.caliverse.admin.logs.logservice.indicators;

import com.caliverse.admin.Indicators.Indicatordomain.IndicatorsLog;
import com.caliverse.admin.Indicators.Indicatorsservice.base.IndicatorsLogLoadServiceBase;
import com.caliverse.admin.Indicators.entity.CurrencyItemLogInfo;
import com.caliverse.admin.Indicators.entity.CurrencyLogInfo;
import com.caliverse.admin.Indicators.indicatorrepository.IndicatorCurrencyRepository;
import com.caliverse.admin.domain.entity.common.SearchUserType;
import com.caliverse.admin.global.common.constants.AdminConstants;
import com.caliverse.admin.global.common.utils.DataHelper;
import com.caliverse.admin.logs.Indicatordomain.CurrencyMongoLog;
import com.caliverse.admin.logs.logservice.businesslogservice.BusinessLogCurrencyService;
import com.caliverse.admin.mongodb.dto.MongoPageResult;
import com.mongodb.BasicDBObject;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
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.data.mongodb.core.aggregation.VariableOperators.Let;
import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Reduce;
import org.springframework.data.mongodb.core.aggregation.StringOperators.Concat;
import static org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable.newVariable;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;


@Slf4j
@Service
public class IndicatorsCurrencyService extends IndicatorsLogLoadServiceBase {

    @Autowired private IndicatorCurrencyRepository indicatorCurrencyRepository;
    @Autowired private BusinessLogCurrencyService currencyService;

    public IndicatorsCurrencyService(@Qualifier("mongoIndicatorTemplate") MongoTemplate mongoTemplate) {
        super(mongoTemplate);
    }

    public void collectCurrency(String startTime, String endTime){
        String logTimeStr = startTime.substring(0, 10);

        List<CurrencyMongoLog> indicatorsLog = null;
        indicatorsLog = currencyService.loadBusinessLogData(startTime, endTime, CurrencyMongoLog.class);

        if (indicatorsLog == null || indicatorsLog.isEmpty()) {
            log.info("collectCurrency indicatorsLog Log logDay: {} null", logTimeStr);
            return;
        }

        for(CurrencyMongoLog mongo_log : indicatorsLog){
            String logDay = mongo_log.getLogDay();

            CurrencyLogInfo currencyLog = new CurrencyLogInfo(logDay,
                    mongo_log.getAccountId(),
                    mongo_log.getUserGuid(),
                    mongo_log.getUserNickname(),
                    mongo_log.getSapphireAcquired(),
                    mongo_log.getSapphireConsumed(),
                    mongo_log.getSapphireNet(),
                    mongo_log.getGoldAcquired(),
                    mongo_log.getGoldConsumed(),
                    mongo_log.getGoldNet(),
                    mongo_log.getCaliumAcquired(),
                    mongo_log.getCaliumConsumed(),
                    mongo_log.getCaliumNet(),
                    mongo_log.getBeamAcquired(),
                    mongo_log.getBeamConsumed(),
                    mongo_log.getBeamNet(),
                    mongo_log.getRubyAcquired(),
                    mongo_log.getRubyConsumed(),
                    mongo_log.getRubyNet(),
                    mongo_log.getTotalCurrencies(),
                    mongo_log.getCurrencies()
            );
            saveStatLogData(currencyLog);
        }
        log.info("collectCurrency Currency Log Save logDay: {}", logTimeStr);
    }

    private void saveStatLogData(IndicatorsLog indicatorsLog) {

        if (indicatorsLog instanceof CurrencyLogInfo logInfo) {
            try {
                boolean exists = indicatorCurrencyRepository.existsByLogDayAndUserGuid(
                        logInfo.getLogDay(),
                        logInfo.getUserGuid()
                );
                if (exists) {
                    log.debug("Duplicate data skipped - logDay: {}, userGuid: {}",
                            logInfo.getLogDay(), logInfo.getUserGuid());
                    return;
                }
                indicatorCurrencyRepository.save(logInfo);
            } catch (Exception e) {
                log.error("Error Repository Write CurrencyLogInfo: {}, Message: {}", logInfo, e.getMessage());
            }
        } else {
            log.error("Not instanceof CurrencyLogInfo");
        }
    }

    @Override
    public <T extends IndicatorsLog> List<T> getIndicatorsLogData(String startTime, String endTime, Class<T> clazz) {
        Criteria criteria = makeCriteria(startTime, endTime, AdminConstants.MONGO_DB_KEY_LOGDAY);

        ProjectionOperation projection = Aggregation.project()
                .and("_id").as("id")
                .and(AdminConstants.MONGO_DB_KEY_LOGDAY).as("logDay")
                .and(AdminConstants.MONGO_DB_KEY_ACCOUNT_ID).as("accountId")
                .and(AdminConstants.MONGO_DB_KEY_USER_GUID).as("userGuid")
                .and(AdminConstants.MONGO_DB_KEY_USER_NICKNAME).as("userNickname")
                .and("sapphireAcquired").as("sapphireAcquired")
                .and("sapphireConsumed").as("sapphireConsumed")
                .and("sapphireNet").as("sapphireNet")
                .and("goldAcquired").as("goldAcquired")
                .and("goldConsumed").as("goldConsumed")
                .and("goldNet").as("goldNet")
                .and("caliumAcquired").as("caliumAcquired")
                .and("caliumConsumed").as("caliumConsumed")
                .and("caliumNet").as("caliumNet")
                .and("beamAcquired").as("beamAcquired")
                .and("beamConsumed").as("beamConsumed")
                .and("beamNet").as("beamNet")
                .and("rubyAcquired").as("rubyAcquired")
                .and("rubyConsumed").as("rubyConsumed")
                .and("rubyNet").as("rubyNet")
                .and("totalCurrencies").as("totalCurrencies")
                .and("currencies").as("currencies");

        List<AggregationOperation> operations = List.of(
                Aggregation.match(criteria),
                projection,
                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_CURRENCY
                , clazz
        ).getMappedResults();
    }

    public <T extends IndicatorsLog> MongoPageResult<T> getCurrencyDetailLogData(
            String searchType,
            String searchData,
            String tranId,
            String logAction,
            String currencyType,
            String amountDeltaType,
            String startTime,
            String endTime,
            String orderBy,
            Integer page,
            Integer size,
            Class<T> clazz
    ) {
        Criteria criteria = makeCriteria(startTime, endTime, AdminConstants.MONGO_DB_KEY_LOGDAY);

        if(tranId != null && !tranId.isEmpty()){
            criteria.and("currencies.tranId").is(tranId);
        }

        if(searchData != null && !searchData.isEmpty()){
            switch (searchType){
                case "GUID":
                    criteria.and("userGuid").is(searchData);
                    break;
                case "NICKNAME":
                    criteria.and("userNickname").is(searchData);
                    break;
                case "ACCOUNT":
                    criteria.and("accountId").is(searchData);
                    break;
                default:
                    log.error("searchType: {} not support", searchType);
                    return null;
            }
        }

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

        ProjectionOperation projection = Aggregation.project()
                .and("_id").as("id")
                .and(AdminConstants.MONGO_DB_KEY_LOGDAY).as("logDay")
                .and(AdminConstants.MONGO_DB_KEY_ACCOUNT_ID).as("accountId")
                .and(AdminConstants.MONGO_DB_KEY_USER_GUID).as("userGuid")
                .and(AdminConstants.MONGO_DB_KEY_USER_NICKNAME).as("userNickname")
                .and("currencies.tranId").as("tranId")
                .and("currencies.logTime").as("logTime")
                .and("currencies.action").as("action")
                .and("currencies.currencyType").as("currencyType")
                .and("currencies.amountDeltaType").as("amountDeltaType")
                .and("currencies.deltaAmount").as("deltaAmount")
                .and("currencies.currencyAmount").as("currencyAmount");

        List<AggregationOperation> baseOperations = new ArrayList<>(List.of(
                unwindOperation,
                Aggregation.match(criteria),
                projection,
                Aggregation.sort(orderBy.equals("DESC") ? Sort.Direction.DESC : Sort.Direction.ASC, AdminConstants.MONGO_DB_KEY_LOGTIME)
        ));

        Criteria postUnwindCriteria = new Criteria();
        if(logAction != null && !logAction.isEmpty() && !logAction.equals("None")){
            postUnwindCriteria.and("action").is(logAction);
        }

        if(currencyType != null && !currencyType.isEmpty() && !currencyType.equals("None")){
            postUnwindCriteria.and("currencyType").is(currencyType);
        }

        if(amountDeltaType != null && !amountDeltaType.isEmpty() && !amountDeltaType.equals("None")){
            postUnwindCriteria.and("amountDeltaType").is(amountDeltaType);
        }

        if (!postUnwindCriteria.getCriteriaObject().isEmpty()) {
            baseOperations.add(Aggregation.match(postUnwindCriteria));
        }

        int totalCount = 0;
        if(page != null && page != 0) {
            List<AggregationOperation> countOperations = new ArrayList<>(baseOperations);
            countOperations.add(Aggregation.count().as("total"));

            Aggregation countAggregation = Aggregation.newAggregation(countOperations);
            Document countResult = mongoTemplate.aggregate(
                            countAggregation.withOptions(AggregationOptions.builder().allowDiskUse(true).build()),
                            AdminConstants.MONGO_DB_COLLECTION_CURRENCY,
                            Document.class
                    ).getUniqueMappedResult();
            totalCount = countResult != null ? countResult.getInteger("total") : 0;
        }

        List<AggregationOperation> dataOperations = new ArrayList<>(baseOperations);
        if(page != null && page != 0) {
            int skip = (page - 1) * size;
            dataOperations.add(Aggregation.skip((long) skip));
            dataOperations.add(Aggregation.limit(size));
        }

        Aggregation aggregation = Aggregation.newAggregation(dataOperations);
        List<T> items = mongoTemplate.aggregate(
                aggregation.withOptions(AggregationOptions.builder().allowDiskUse(true).build()),
                        AdminConstants.MONGO_DB_COLLECTION_CURRENCY,
                        clazz
                ).getMappedResults();

        return new MongoPageResult<>(items, totalCount);
    }

    public <T extends IndicatorsLog> MongoPageResult<T> getCurrencyItemLogData(
            String searchType,
            String searchData,
            String tranId,
            String logAction,
            String currencyType,
            String amountDeltaType,
            String startTime,
            String endTime,
            String orderBy,
            Integer page,
            Integer size,
            Class<T> clazz
    ) {
        Criteria criteria = makeCriteria(startTime, endTime, AdminConstants.MONGO_DB_KEY_LOGDAY);

        if(tranId != null && !tranId.isEmpty()){
            criteria.and("currencies.tranId").is(tranId);
        }

        if(searchData != null && !searchData.isEmpty()){
            switch (searchType){
                case "GUID":
                    criteria.and("userGuid").is(searchData);
                    break;
                case "NICKNAME":
                    criteria.and("userNickname").is(searchData);
                    break;
                case "ACCOUNT":
                    criteria.and("accountId").is(searchData);
                    break;
                default:
                    log.error("searchType: {} not support", searchType);
                    return null;
            }
        }

        Criteria postUnwindCriteria = new Criteria();
        if(logAction != null && !logAction.isEmpty() && !logAction.equals("None")){
            postUnwindCriteria.and("currencies.action").is(logAction);
        }
        if(currencyType != null && !currencyType.isEmpty() && !currencyType.equals("None")){
            postUnwindCriteria.and("currencies.currencyType").is(currencyType);
        }
        if(amountDeltaType != null && !amountDeltaType.isEmpty() && !amountDeltaType.equals("None")){
            postUnwindCriteria.and("currencies.amountDeltaType").is(amountDeltaType);
        }

        int totalCount = 0;

        // 카운트 계산
        if(page != null && page != 0) {
            totalCount = getOptimizedCount(criteria, postUnwindCriteria);
        }

        List<T> items = getDataWithLookup(criteria, postUnwindCriteria, startTime, endTime, orderBy, page, size, clazz);

        return new MongoPageResult<>(items, totalCount);
    }

    // 최적화된 카운트 메소드
    private int getOptimizedCount(Criteria criteria, Criteria postUnwindCriteria) {
        List<AggregationOperation> countOperations = new ArrayList<>();

        countOperations.add(Aggregation.match(criteria));

        countOperations.add(Aggregation.unwind("currencies"));

        if (!postUnwindCriteria.getCriteriaObject().isEmpty()) {
            countOperations.add(Aggregation.match(postUnwindCriteria));
        }

        countOperations.add(Aggregation.count().as("total"));

        Aggregation countAggregation = Aggregation.newAggregation(countOperations);
        Document countResult = mongoTemplate.aggregate(
                countAggregation.withOptions(AggregationOptions.builder().allowDiskUse(true).build()),
                AdminConstants.MONGO_DB_COLLECTION_CURRENCY,
                Document.class
        ).getUniqueMappedResult();

        return countResult != null ? countResult.getInteger("total") : 0;
    }

    // 데이터 조회 메소드
    private <T extends IndicatorsLog> List<T> getDataWithLookup(
            Criteria criteria,
            Criteria postUnwindCriteria,
            String startTime,
            String endTime,
            String orderBy,
            Integer page,
            Integer size,
            Class<T> clazz) {

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

        //자바에서lookup은 성은부하가크다
//        AggregationOperation lookup = context -> new Document("$lookup",
//                new Document("from", "item")
//                        .append("let", new Document("tranId", "$currencies.tranId"))
//                        .append("pipeline", Arrays.asList(
//                                new Document("$match",
//                                        new Document("logDay",
//                                                new Document("$gte", startTime)
//                                                        .append("$lte", endTime))),
//                                new Document("$unwind", "$itemDetails"),
//                                new Document("$match",
//                                        new Document("$expr",
//                                                new Document("$eq", Arrays.asList("$itemDetails.tranId", "$$tranId")))),
//                                new Document("$unwind", "$itemDetails.items"),
//                                new Document("$group",
//                                        new Document("_id", "$itemDetails.tranId")
//                                                .append("itemMIDs", new Document("$addToSet", "$itemDetails.items.itemMID"))),
//                                new Document("$project",
//                                        new Document("tranId", "$_id")
//                                                .append("itemMIDString",
//                                                        new Document("$reduce",
//                                                                new Document("input", "$itemMIDs")
//                                                                        .append("initialValue", "")
//                                                                        .append("in",
//                                                                                new Document("$cond", Arrays.asList(
//                                                                                        new Document("$eq", Arrays.asList("$$value", "")),
//                                                                                        new Document("$toString", "$$this"),
//                                                                                        new Document("$concat", Arrays.asList("$$value", "|", new Document("$toString", "$$this")))
//                                                                                ))))))
//                        ))
//                        .append("as", "itemInfo"));

        ProjectionOperation projection = Aggregation.project()
                .andExclude("_id")
                .and(AdminConstants.MONGO_DB_KEY_LOGDAY).as("logDay")
                .and(AdminConstants.MONGO_DB_KEY_ACCOUNT_ID).as("accountId")
                .and(AdminConstants.MONGO_DB_KEY_USER_GUID).as("userGuid")
                .and(AdminConstants.MONGO_DB_KEY_USER_NICKNAME).as("userNickname")
                .and("currencies.tranId").as("tranId")
                .and("currencies.logTime").as("logTime")
                .and("currencies.action").as("action")
                .and("currencies.currencyType").as("currencyType")
                .and("currencies.amountDeltaType").as("amountDeltaType")
                .and("currencies.deltaAmount").as("deltaAmount")
                .and("currencies.currencyAmount").as("currencyAmount")
                .and(ConditionalOperators.ifNull(
                                ArrayOperators.arrayOf("itemInfo.itemMIDString").elementAt(0))
                        .then("")).as("itemIDs");

        List<AggregationOperation> dataOperations = new ArrayList<>();

        dataOperations.add(unwindOperation);

        dataOperations.add(Aggregation.match(criteria));

        if (!postUnwindCriteria.getCriteriaObject().isEmpty()) {
            dataOperations.add(Aggregation.match(postUnwindCriteria));
        }

//        dataOperations.add(lookup);

        dataOperations.add(Aggregation.sort(orderBy.equals("DESC") ? Sort.Direction.DESC : Sort.Direction.ASC, "currencies.logTime"));

        if(page != null && page != 0) {
            int skip = (page - 1) * size;
            dataOperations.add(Aggregation.skip((long) skip));
            dataOperations.add(Aggregation.limit(size));
        }

        dataOperations.add(projection);

        Aggregation aggregation = Aggregation.newAggregation(dataOperations);
        return mongoTemplate.aggregate(
                aggregation.withOptions(AggregationOptions.builder().allowDiskUse(true).build()),
                AdminConstants.MONGO_DB_COLLECTION_CURRENCY,
                clazz
        ).getMappedResults();
    }

}
