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.UserLoginLogInfo;
import com.caliverse.admin.Indicators.indicatorrepository.IndicatorUserLoginRepository;
import com.caliverse.admin.global.common.constants.AdminConstants;
import com.caliverse.admin.logs.Indicatordomain.UserLoginMongoLog;
import com.caliverse.admin.logs.logservice.businesslogservice.BusinessLogUserLoginMultiService;
import com.caliverse.admin.logs.logservice.businesslogservice.BusinessLogUserLoginService;
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 IndicatorsUserLoginService extends IndicatorsLogLoadServiceBase {

    @Autowired private IndicatorUserLoginRepository indicatorUserLoginRepository;
    @Autowired private BusinessLogUserLoginService userLoginService;
    @Autowired private BusinessLogUserLoginMultiService userLoginMultiService;

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

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

        List<UserLoginMongoLog> indicatorsLog = null;
        if(startTime.startsWith("2024")){
            indicatorsLog = userLoginMultiService.loadBusinessLogData(startTime, endTime, UserLoginMongoLog.class);
        }else{
            indicatorsLog = userLoginService.loadBusinessLogData(startTime, endTime, UserLoginMongoLog.class);
        }

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

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

            UserLoginLogInfo userCreateLog = new UserLoginLogInfo(logDay,
                    mongo_log.getAccountId(),
                    mongo_log.getUserGuid(),
                    mongo_log.getUserNickname(),
                    mongo_log.getSessions(),
                    mongo_log.getFirstLoginTime(),
                    mongo_log.getLastLogoutTime(),
                    mongo_log.getLoginCount(),
                    mongo_log.getTotalPlayTime()
            );
            saveStatLogData(userCreateLog);
        }
        log.info("collectUserLogin UserLogin Log Save logDay: {}", logTimeStr);
    }

    public void saveStatLogData(IndicatorsLog indicatorsLog) {

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

    @Override
    public <T extends IndicatorsLog> List<T> getIndicatorsLogData(String startTime, String endTime, Class<T> clazz) {
        return List.of();
    }

    public <T extends IndicatorsLog> MongoPageResult<T> getLoginDetailLogData(
            String searchType,
            String searchData,
            String tranId,
            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("sessions");

        if(tranId != null && !tranId.isEmpty()){
            criteria.and("sessions.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("sessions.tranId").as("tranId")
                .and("sessions.loginTime").as("loginTime")
                .and("sessions.logoutTime").as("logoutTime")
                .and("sessions.serverType").as("serverType")
                .and("sessions.ip").as("ip")
                .and("sessions.languageType").as("languageType")
                .and("sessions.playtime").as("playtime");

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

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

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