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.SnapshotLogInfo;
import com.caliverse.admin.Indicators.indicatorrepository.IndicatorSnapshotRepository;
import com.caliverse.admin.global.common.constants.AdminConstants;
import com.caliverse.admin.logs.Indicatordomain.SnapshotMongoLog;
import com.caliverse.admin.logs.logservice.businesslogservice.BusinessLogSnapshotService;
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 IndicatorsSnapshotService extends IndicatorsLogLoadServiceBase {

    @Autowired private IndicatorSnapshotRepository indicatorSnapshotRepository;
    @Autowired private BusinessLogSnapshotService snapshotService;

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

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

        List<SnapshotMongoLog> indicatorsLog = null;
        indicatorsLog = snapshotService.loadBusinessLogData("2024-08-28", endTime, SnapshotMongoLog.class);

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

        for(SnapshotMongoLog mongo_log : indicatorsLog){

            SnapshotLogInfo snapshotLog = new SnapshotLogInfo(logTimeStr,
                    mongo_log.getAccountId(),
                    mongo_log.getUserGuid(),
                    mongo_log.getUserNickname(),
                    mongo_log.getGold(),
                    mongo_log.getSapphire(),
                    mongo_log.getCalium(),
                    mongo_log.getRuby(),
                    mongo_log.getItem_11570001(),
                    mongo_log.getItem_11570002(),
                    mongo_log.getItem_13080002(),
                    mongo_log.getItem_13080004(),
                    mongo_log.getItem_13080005(),
                    mongo_log.getItem_13080006(),
                    mongo_log.getItem_13080007(),
                    mongo_log.getItem_13080008(),
                    mongo_log.getItem_13080009(),
                    mongo_log.getLastLogoutTime()
            );
            saveStatLogData(snapshotLog);
        }
        log.info("collectSnapshot Snapshot Log Save logDay: {}", logTimeStr);
    }

    private void saveStatLogData(IndicatorsLog indicatorsLog) {

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

    @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()
                .andExclude("_id")
                .and(AdminConstants.MONGO_DB_KEY_LOGDAY).as(AdminConstants.MONGO_DB_KEY_LOGDAY)
                .and(AdminConstants.MONGO_DB_KEY_ACCOUNT_ID).as(AdminConstants.MONGO_DB_KEY_ACCOUNT_ID)
                .and(AdminConstants.MONGO_DB_KEY_USER_GUID).as(AdminConstants.MONGO_DB_KEY_USER_GUID)
                .and(AdminConstants.MONGO_DB_KEY_USER_NICKNAME).as(AdminConstants.MONGO_DB_KEY_USER_NICKNAME)
                .and("sapphire").as("sapphire")
                .and("gold").as("gold")
                .and("calium").as("calium")
                .and("ruby").as("ruby")
                .and("item_11570001").as("item_11570001")
                .and("item_11570002").as("item_11570002")
                .and("item_13080002").as("item_13080002")
                .and("item_13080004").as("item_13080004")
                .and("item_13080005").as("item_13080005")
                .and("item_13080006").as("item_13080006")
                .and("item_13080007").as("item_13080007")
                .and("item_13080008").as("item_13080008")
                .and("item_13080009").as("item_13080009")
                .and("lastLogoutTime").as("lastLogoutTime");

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

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

        if(searchData != null && !searchData.isEmpty()){
            switch (searchType){
                case "GUID":
                    criteria.and(AdminConstants.MONGO_DB_KEY_USER_GUID).is(searchData);
                    break;
                case "NICKNAME":
                    criteria.and(AdminConstants.MONGO_DB_KEY_USER_NICKNAME).is(searchData);
                    break;
                case "ACCOUNT":
                    criteria.and(AdminConstants.MONGO_DB_KEY_ACCOUNT_ID).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(AdminConstants.MONGO_DB_KEY_LOGDAY)
                .and(AdminConstants.MONGO_DB_KEY_ACCOUNT_ID).as(AdminConstants.MONGO_DB_KEY_ACCOUNT_ID)
                .and(AdminConstants.MONGO_DB_KEY_USER_GUID).as(AdminConstants.MONGO_DB_KEY_USER_GUID)
                .and(AdminConstants.MONGO_DB_KEY_USER_NICKNAME).as(AdminConstants.MONGO_DB_KEY_USER_NICKNAME)
                .and("sapphire").as("sapphire")
                .and("gold").as("gold")
                .and("calium").as("calium")
                .and("ruby").as("ruby")
                .and("item_11570001").as("item_11570001")
                .and("item_11570002").as("item_11570002")
                .and("item_13080002").as("item_13080002")
                .and("item_13080004").as("item_13080004")
                .and("item_13080005").as("item_13080005")
                .and("item_13080006").as("item_13080006")
                .and("item_13080007").as("item_13080007")
                .and("item_13080008").as("item_13080008")
                .and("item_13080009").as("item_13080009")
                .and("lastLogoutTime").as("lastLogoutTime");

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

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

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

}
