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

import com.caliverse.admin.domain.entity.common.SearchUserType;
import com.caliverse.admin.domain.request.LogGenericRequest;
import com.caliverse.admin.global.common.constants.AdminConstants;
import com.caliverse.admin.logs.entity.LogAction;
import com.caliverse.admin.logs.entity.LogDomain;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.formula.functions.T;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

import java.util.*;

@Slf4j
@Service
public class BusinessLogGenericService extends BusinessLogServiceBase {


    public BusinessLogGenericService(@Qualifier("mongoBusinessLogTemplate") MongoTemplate mongoTemplate) {
        super(mongoTemplate);
    }


//    public List<Map<String, Object>> loadBusinessLogData(LogGenericRequest logGenericRequest) {
    public <T> List<T> loadBusinessLogData(LogGenericRequest logGenericRequest, Class<T> class1) {
        String startTime = logGenericRequest.getStartDt().toString().substring(0, 10);
        String endTime = logGenericRequest.getEndDt().toString().substring(0, 10);
        LogAction logAction = logGenericRequest.getLogAction();
        LogDomain logDomain = logGenericRequest.getLogDomain();
        SearchUserType searchUserType = logGenericRequest.getSearchType();
        String searchData = logGenericRequest.getSearchData();
        String tranId = logGenericRequest.getTranId();

        Criteria criteria = makeCriteria(startTime, endTime);
        List<AggregationOperation> operations = setDefaultOperation(criteria);

        //message json parsing
        operations.add(context ->
                new Document("$addFields",
                        new Document("parseMessage",
                                new Document("$function",
                                        new Document("body", "function(jsonString) { try { return JSON.parse(jsonString); } catch(e) { return {}; } }")
                                                .append("args", Arrays.asList("$message"))
                                                .append("lang", "js")
                                )
                        )
                )
        );

        operations.add(context ->
                new Document("$addFields",
                        new Document("header","$parseMessage.Header")
                                .append("body","$parseMessage.Body")
                )
        );

        if(logAction != null && !logAction.equals(LogAction.None)) {
            operations.add(context ->
                new Document("$match",
                        new Document(AdminConstants.MONGO_DB_KEY_ACTION, logAction.name())
                )
            );
        }

        if(logDomain != null && !logDomain.equals(LogDomain.BASE)) {
            operations.add(context ->
                    new Document("$match",
                            new Document(AdminConstants.MONGO_DB_KEY_DOMAIN, logDomain.name())
                    )
            );
        }

        if(searchUserType != null && !searchData.isEmpty() && !searchUserType.equals(SearchUserType.NONE)) {
            switch(searchUserType){
                case ACCOUNT:
                    operations.add(context ->
                            new Document("$match",
                                    new Document(AdminConstants.MONGO_DB_KEY_ACCOUNT_ID, searchData)
                            )
                    );
                    break;
                case GUID:
                    operations.add(context ->
                            new Document("$match",
                                    new Document(AdminConstants.MONGO_DB_KEY_USER_GUID, searchData)
                            )
                    );
                    break;
                case NICKNAME:
                    operations.add(context ->
                            new Document("$match",
                                    new Document(AdminConstants.MONGO_DB_KEY_USER_NICKNAME, searchData)
                            )
                    );
                    break;
            }
        }

        if(tranId != null && !tranId.isEmpty()) {
            operations.add(context ->
                    new Document("$match",
                            new Document(AdminConstants.MONGO_DB_KEY_TRAN_ID, tranId)
                    )
            );
        }

        // 최종 출력 형식
        operations.add(context ->
                new Document("$sort",
                        new Document("logTime", logGenericRequest.getOrderBy().equals("ASC") ? 1 : -1)
                                .append(AdminConstants.MONGO_DB_KEY_TRAN_ID, 1)
                )
        );

        Aggregation aggregation = Aggregation.newAggregation(operations);
        log.info("loadBusinessLogData Generic Query: {}", aggregation);
//        try {
            AggregationResults<T> results = getMongoTemplate()
                    .aggregate(aggregation, AdminConstants.MONGO_DB_COLLECTION_LOG, class1);
            return results.getMappedResults();
//        }catch(Exception e){
//            log.error("BusinessLog query error", e);
//            return null;
//        }


//        AggregationResults<Document> results = getMongoTemplate().aggregate(aggregation, AdminConstants.MONGO_DB_COLLECTION_LOG, Document.class);
//
//        List<Map<String, Object>> logMaps = new ArrayList<>();
//        for (Document doc : results.getMappedResults()) {
//            logMaps.add(flattenDocument(doc));
//        }
//
//        return logMaps;
    }

    protected Criteria makeCriteria(String startTime, String endTime) {
        return new Criteria()
                .andOperator(
                        Criteria.where(AdminConstants.MONGO_DB_KEY_LOGTIME).gte(startTime).lt(endTime)
                );
    }

    private Map<String, Object> flattenDocument(Document document) {
        Map<String, Object> result = new LinkedHashMap<>();
        flattenDocumentRecursive(document, "", result);
        return result;
    }

    private void flattenDocumentRecursive(Object obj, String prefix, Map<String, Object> result) {
        if (obj instanceof Document) {
            Document doc = (Document) obj;
            for (String key : doc.keySet()) {
                Object value = doc.get(key);
                String newKey = prefix.isEmpty() ? key : prefix + "." + key;

                if (value instanceof Document || value instanceof Map) {
                    flattenDocumentRecursive(value, newKey, result);
                } else if (value instanceof List) {
                    // 리스트의 각 항목에 대해 처리
                    List<?> list = (List<?>) value;
                    result.put(newKey, list);

                    // 리스트 항목이 복잡한 객체인 경우 각각 평탄화
                    for (int i = 0; i < list.size(); i++) {
                        Object listItem = list.get(i);
                        if (listItem instanceof Document || listItem instanceof Map) {
                            flattenDocumentRecursive(listItem, newKey + "[" + i + "]", result);
                        }
                    }
                } else {
                    result.put(newKey, value);
                }
            }
        } else if (obj instanceof Map) {
            Map<?, ?> map = (Map<?, ?>) obj;
            for (Map.Entry<?, ?> entry : map.entrySet()) {
                String key = entry.getKey().toString();
                Object value = entry.getValue();
                String newKey = prefix.isEmpty() ? key : prefix + "." + key;

                if (value instanceof Document || value instanceof Map) {
                    flattenDocumentRecursive(value, newKey, result);
                } else if (value instanceof List) {
                    // 리스트의 각 항목에 대해 처리
                    List<?> list = (List<?>) value;
                    result.put(newKey, list);

                    // 리스트 항목이 복잡한 객체인 경우 각각 평탄화
                    for (int i = 0; i < list.size(); i++) {
                        Object listItem = list.get(i);
                        if (listItem instanceof Document || listItem instanceof Map) {
                            flattenDocumentRecursive(listItem, newKey + "[" + i + "]", result);
                        }
                    }
                } else {
                    result.put(newKey, value);
                }
            }
        }
    }
}
