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.CurrencyLogInfo;
import com.caliverse.admin.Indicators.entity.ItemLogInfo;
import com.caliverse.admin.Indicators.indicatorrepository.IndicatorCurrencyRepository;
import com.caliverse.admin.Indicators.indicatorrepository.IndicatorItemRepository;
import com.caliverse.admin.global.common.constants.AdminConstants;
import com.caliverse.admin.logs.Indicatordomain.CurrencyMongoLog;
import com.caliverse.admin.logs.Indicatordomain.ItemMongoLog;
import com.caliverse.admin.logs.logservice.businesslogservice.BusinessLogCurrencyService;
import com.caliverse.admin.logs.logservice.businesslogservice.BusinessLogItemService;
import com.caliverse.admin.mongodb.dto.MongoPageResult;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
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.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Service
public class IndicatorsItemService extends IndicatorsLogLoadServiceBase {

    @Autowired private IndicatorItemRepository indicatorItemRepository;
    @Autowired private BusinessLogItemService itemService;

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

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

        List<ItemMongoLog> indicatorsLog = null;
        indicatorsLog = itemService.loadBusinessLogData(startTime, endTime, ItemMongoLog.class);

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

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

            ItemLogInfo itemLog = new ItemLogInfo(
                    logDay,
                    mongo_log.getAccountId(),
                    mongo_log.getUserGuid(),
                    mongo_log.getUserNickname(),
                    mongo_log.getTotalItems(),
                    mongo_log.getItemDetails(),
                    mongo_log.getItemTypeLargeStats()
            );
            saveStatLogData(itemLog);
        }
        log.info("collectItem Item Log Save logDay: {}", logTimeStr);
    }

    private void saveStatLogData(IndicatorsLog indicatorsLog) {

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

    @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");

        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_ITEM
                , clazz
        ).getMappedResults();
    }

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

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

        if(tranId != null && !tranId.isEmpty()){
            criteria.and("itemDetails.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;
            }
        }

        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("itemDetails.tranId").as("tranId")
                .and("itemDetails.logTime").as("logTime")
                .and("itemDetails.items").as("items");

        UnwindOperation unwindOperation2 = Aggregation.unwind("items");

        ProjectionOperation projection2 = Aggregation.project()
                .andInclude("logDay", "accountId", "userGuid", "userNickname", "tranId", "logTime")
                .and("items.itemMID").as("itemId")
                .and("items.itemName").as("itemName")
                .and("items.action").as("action")
                .and("items.itemTypeLarge").as("itemTypeLarge")
                .and("items.itemTypeSmall").as("itemTypeSmall")
                .and("items.countDeltaType").as("countDeltaType")
                .and("items.deltaCount").as("deltaCount")
                .and("items.stackCount").as("stackCount")
                .and("itemDetails.items").as("items");

        List<AggregationOperation> baseOperations = new ArrayList<>(List.of(
                unwindOperation,
                Aggregation.match(criteria),
                projection,
                unwindOperation2,
                projection2,
                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(itemLargeType != null && !itemLargeType.isEmpty() && !itemLargeType.equals("None")){
            postUnwindCriteria.and("itemTypeLarge").is(itemLargeType);
        }

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

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

        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_ITEM,
                            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_ITEM,
                        clazz
                ).getMappedResults();

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