package com.caliverse.admin.domain.service;

import com.caliverse.admin.domain.RabbitMq.MessageHandlerService;
import com.caliverse.admin.domain.RabbitMq.message.IntervalType;
import com.caliverse.admin.domain.dao.admin.RankMapper;
import com.caliverse.admin.domain.datacomponent.MetaDataHandler;
import com.caliverse.admin.domain.entity.ERankingType;
import com.caliverse.admin.domain.entity.RankingSchedule;
import com.caliverse.admin.domain.entity.log.LogAction;
import com.caliverse.admin.domain.request.RankRequest;
import com.caliverse.admin.domain.response.RankResponse;
import com.caliverse.admin.dynamodb.domain.atrrib.*;
import com.caliverse.admin.dynamodb.domain.doc.RankDoc;
import com.caliverse.admin.dynamodb.domain.doc.RankerSnapshotDoc;
import com.caliverse.admin.dynamodb.domain.doc.RankingSnapshotDoc;
import com.caliverse.admin.dynamodb.entity.EScoreType;
import com.caliverse.admin.dynamodb.service.DynamodbRankService;
import com.caliverse.admin.dynamodb.service.DynamodbUserService;
import com.caliverse.admin.global.common.annotation.BusinessProcess;
import com.caliverse.admin.global.common.annotation.RequestLog;
import com.caliverse.admin.global.common.code.CommonCode;
import com.caliverse.admin.global.common.code.ErrorCode;
import com.caliverse.admin.global.common.code.SuccessCode;
import com.caliverse.admin.global.common.exception.RestApiException;
import com.caliverse.admin.global.common.utils.CommonUtils;
import com.caliverse.admin.redis.service.RedisUserInfoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;

@Service
@RequiredArgsConstructor
@Slf4j
public class RankService {

    private final RankMapper rankMapper;
    private final MessageHandlerService messageHandlerService;
    private final RedisUserInfoService redisUserInfoService;
    private final DynamodbRankService dynamodbRankService;
    private final DynamodbUserService dynamodbUserService;
    private final UserGameSessionService userGameSessionService;
    private final MetaDataHandler metaDataHandler;

    @RequestLog
    public RankResponse getList(Map requestParam){

        //페이징 처리
        requestParam = CommonUtils.pageSetting(requestParam);

        List<RankingSchedule> list = rankMapper.getRankingScheduleList(requestParam);

        return RankResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .resultData(RankResponse.ResultData.builder()
                        .rankingScheduleList(list)
                        .total(rankMapper.getTotal())
                        .totalAll(list.size())
                        .pageNo(requestParam.get("page_no")!=null?
                                Integer.parseInt(requestParam.get("page_no").toString()):1)
                        .build()
                )
                .build();
    }

    @RequestLog
    public RankResponse getList(){

        List<RankingSchedule> list = rankMapper.getRankingScheduleSimpleList();

        return RankResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .resultData(RankResponse.ResultData.builder()
                        .rankingScheduleList(list)
                        .build()
                )
                .build();
    }

    @RequestLog
    public RankResponse getDetail(Long id){
        RankingSchedule rankingSchedule = rankMapper.getRankingScheduleDetail(id);

        return RankResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .resultData(RankResponse.ResultData.builder()
                        .rankingSchedule(rankingSchedule)
                        .build())
                .build();
    }

    @RequestLog
    public RankResponse getRankingSnapshotList(String guid){
        List<RankingSnapshotDoc> rankDocList = dynamodbRankService.getRankingSnapshotList(guid);

        List<RankResponse.RankingSnapshotInfo> rankingInfoList = rankDocList.stream()
                .map(doc -> {
                    RankingSnapshotAttrib attrib = doc.getAttribValue();

                    return RankResponse.RankingSnapshotInfo.builder()
                            .rankingGuid(guid)
                            .snapshotIndex(attrib.getSnapshotIndex())
                            .snapshotTime(attrib.getSnapshotTime())
                            .build();
                }).toList();

        return RankResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .resultData(RankResponse.ResultData.builder()
                        .rankingSnapshotList(rankingInfoList)
                        .build())
                .build();
    }

    @RequestLog
    public RankResponse getRankerList(String guid, Integer snapshotIndex){
        List<RankerSnapshotDoc> rankDocList = dynamodbRankService.getRankerSnapshotList(guid, snapshotIndex);

        List<RankResponse.UserRankerInfo> userRankerInfoList = rankDocList.stream()
                .sorted(Comparator.comparing(doc -> doc.getAttribValue().getRankNum()))
                .map(doc -> {
                    RankerSnapshotAttrib attrib = doc.getAttribValue();

                    Integer rankNum = attrib.getRankNum() == null ? 0 : attrib.getRankNum();

                    String userGuid = attrib.getRankerGuid();
                    String nickname = dynamodbUserService.getGuidByName(userGuid);
                    String accountId = dynamodbUserService.getGuidByAccountId(userGuid);

                    return RankResponse.UserRankerInfo.builder()
                            .rank(rankNum)
                            .score(attrib.getScore())
                            .scoreType(attrib.getScoreType())
                            .userGuid(userGuid)
                            .nickname(nickname)
                            .accountId(accountId)
                            .rankingGuid(guid)
                            .build();
                })
                .toList();

        return RankResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .resultData(RankResponse.ResultData.builder()
                        .rankerList(userRankerInfoList)
                        .build())
                .build();
    }

    @RequestLog
    public RankResponse getRankList(String guid){
        List<RankDoc> rankDocList = dynamodbRankService.getRankList(guid);

        List<RankResponse.UserRankerInfo> userRankerInfoList = rankDocList.stream()
                .sorted(Comparator.comparing(doc -> doc.getAttribValue().getRankNum()))
                .map(doc -> {
                    RankAttrib attrib = doc.getAttribValue();

                    String userGuid = attrib.getRankerGuid();
                    String nickname = dynamodbUserService.getGuidByName(userGuid);
                    String accountId = dynamodbUserService.getGuidByAccountId(userGuid);

                    return RankResponse.UserRankerInfo.builder()
                            .rank(attrib.getRankNum())
                            .score(attrib.getScore())
                            .scoreType(attrib.getScoreType())
                            .userGuid(userGuid)
                            .nickname(nickname)
                            .accountId(accountId)
                            .rankingGuid(guid)
                            .build();
        }).toList();

        return RankResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .resultData(RankResponse.ResultData.builder()
                        .rankerList(userRankerInfoList)
                        .build())
                .build();
    }

    @RequestLog
    public RankResponse getRankingInfo(String userGuid){
        Map<String, Object> param = new HashMap<>();
        param.put("status", RankingSchedule.EVENT_STATUS.RUNNING.toString());

        List<RankingSchedule> list = rankMapper.getRankingScheduleList(param);

        RankResponse.UserRankingInfo info = new RankResponse.UserRankingInfo();
        List<RankResponse.UserRankingInfo.RankerItem> rankerItems = new ArrayList<>();
        list.forEach(data -> {
            String rankingGuid = data.getGuid();
            ERankingType rankingType = ERankingType.fromMetaId(data.getMetaId());
            RankerAttrib attrib = dynamodbRankService.getRanker(rankingGuid, userGuid);
            if(attrib == null) return;

            EScoreType scoreType = attrib.getScoreType();
            Integer score = attrib.getScore();

            RankResponse.UserRankingInfo.RankerItem item = RankResponse.UserRankingInfo.RankerItem.builder()
                    .rankingType(rankingType)
                    .score(score)
                    .scoreType(scoreType)
                    .guid(data.getGuid())
                    .build();

            rankerItems.add(item);
        });
        info.setRankingItems(rankerItems);
        info.setUserGuid(userGuid);

        return RankResponse.builder()
                .resultData(
                        RankResponse.ResultData.builder()
                                .userRankingInfo(info)
                                .build()
                )
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .build();

    }

    @BusinessProcess(action = LogAction.RANKING_SCHEDULE)
    @Transactional(transactionManager = "transactionManager")
    @RequestLog
    public RankResponse postRankingSchedule(RankRequest rankRequest){
        rankRequest.setCreateBy(CommonUtils.getAdmin().getId());

        int chk = rankMapper.checkOverlap(rankRequest);
        if(chk > 0){
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.ERROR_SCHEDULE_DUPLICATE.getMessage());
        }

        rankRequest.setGuid(CommonUtils.getSimpleCreateGuId());

        rankMapper.postRankingSchedule(rankRequest);

        long schedule_id = rankRequest.getId();
        HashMap<String,String> map = new HashMap<>();
        map.put("id",String.valueOf(schedule_id));

        log.info("postRankingSchedule Insert Ranking Schedule id: {}", rankRequest.getId());

        RankingSchedule rankingSchedule = rankMapper.getRankingScheduleDetail(rankRequest.getId());

        if(redisUserInfoService.getAllServerList().isEmpty()){
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_FOUND_SERVER.getMessage());
        }

        dynamodbRankService.insertRankingSchedule(rankRequest);

        notifyGameServers("postRankingSchedule", "",null);

        return RankResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .resultData(RankResponse.ResultData.builder()
                        .message(SuccessCode.SAVE.getMessage())
                        .build())
                .build();
    }

    @BusinessProcess(action = LogAction.RANKING_SCHEDULE)
    @Transactional(transactionManager = "transactionManager")
    @RequestLog
    public RankResponse updateRankingSchedule(Long id, RankRequest rankRequest) {
        rankRequest.setId(id);
        rankRequest.setUpdateBy(CommonUtils.getAdmin().getId());
        rankRequest.setUpdateDt(LocalDateTime.now());

        int chk = rankMapper.checkOverlap(rankRequest);
        if(chk > 0){
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.ERROR_SCHEDULE_DUPLICATE.getMessage());
        }

        Long schedule_id = rankRequest.getId();
        RankingSchedule before_info = rankMapper.getRankingScheduleDetail(schedule_id);

        rankMapper.updateRankingSchedule(rankRequest);

        log.info("updateRankingSchedule Update Ranking Schedule Complete: {}", rankRequest.getId());

        RankingSchedule after_info = rankMapper.getRankingScheduleDetail(schedule_id);

        if(redisUserInfoService.getAllServerList().isEmpty()){
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_FOUND_SERVER.getMessage());
        }

        dynamodbRankService.updateRankingSchedule(rankRequest);

        notifyGameServers("updateRankingSchedule", "", null);

        return RankResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .resultData(RankResponse.ResultData.builder()
                        .message(SuccessCode.UPDATE.getMessage())
                        .build())
                .build();
    }

    @BusinessProcess(action = LogAction.RANKER_INFO)
    @RequestLog
    public RankResponse updateRankerInfo(RankRequest rankRequest) {
        String adminUser = CommonUtils.getAdmin().getEmail();

        String userGuid = rankRequest.getUserGuid();
        String rankingGuid = rankRequest.getGuid();

        if(userGuid == null || userGuid.isEmpty() || rankingGuid == null || rankingGuid.isEmpty() ){
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.WARNING_GUID_CHECK.getMessage());
        }

        userGameSessionService.kickUserSession(userGuid, String.format("admin %s Ranker Info Update kick", adminUser));

        dynamodbRankService.updateRanker(rankRequest);

        return RankResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .resultData(RankResponse.ResultData.builder()
                        .message(SuccessCode.UPDATE.getMessage())
                        .build())
                .build();
    }

    @BusinessProcess(action = LogAction.RANKING_UPDATE)
    @RequestLog
    public RankResponse updateRanking(String guid) {
        if(redisUserInfoService.getAllServerList().isEmpty()){
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_FOUND_SERVER.getMessage());
        }

        notifyGameServers("updateRanking", guid, null);

        return RankResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .resultData(RankResponse.ResultData.builder()
                        .message(SuccessCode.UPDATE.getMessage())
                        .build())
                .build();
    }

    @BusinessProcess(action = LogAction.RANKING_UPDATE)
    @RequestLog
    public RankResponse updateSnapshot(String guid) {
        if(redisUserInfoService.getAllServerList().isEmpty()){
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_FOUND_SERVER.getMessage());
        }

        notifyGameServers("updateSnapshot", guid, null);

        return RankResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .resultData(RankResponse.ResultData.builder()
                        .message(SuccessCode.UPDATE.getMessage())
                        .build())
                .build();
    }

    @BusinessProcess(action = LogAction.RANKING_UPDATE)
    @RequestLog
    public RankResponse updateRankingInit(String guid) {
        if(redisUserInfoService.getAllServerList().isEmpty()){
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_FOUND_SERVER.getMessage());
        }

        notifyGameServers("updateRankingInit", guid, null);

        return RankResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .resultData(RankResponse.ResultData.builder()
                        .message(SuccessCode.UPDATE.getMessage())
                        .build())
                .build();
    }

    @BusinessProcess(action = LogAction.RANKING_SCHEDULE)
    @Transactional(transactionManager = "transactionManager")
    @RequestLog
    public RankResponse deleteRankingSchedule(Long id){
        Map<String,Object> map = new HashMap<>();
        map.put("id",id);

        RankingSchedule info = rankMapper.getRankingScheduleDetail(id);

        if(info.getStartDt().isBefore(LocalDateTime.now())){
            return RankResponse.builder()
                    .status(CommonCode.ERROR.getHttpStatus())
                    .result(CommonCode.ERROR.getResult())
                    .resultData(RankResponse.ResultData.builder()
                            .message(ErrorCode.START_DATE_OVER.getMessage())
                            .build())
                    .build();
        }

        rankMapper.deleteRankingSchedule(map);

        log.info("deleteRankingSchedule Delete Ranking Schedule Complete id: {}", id);

        if(redisUserInfoService.getAllServerList().isEmpty()){
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_FOUND_SERVER.getMessage());
        }

        dynamodbRankService.deleteRankingSchedule(info);

        notifyGameServers("deleteRankingSchedule", "", null);

        return RankResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .resultData(RankResponse.ResultData.builder()
                        .message(SuccessCode.DELETE.getMessage())
                        .build())
                .build();
    }

    private void notifyGameServers(String methodName, String guid, Runnable rollbackFunction) {
        // 운영DB 데이터 추가됐다고 게임서버 알림
        List<String> serverList = redisUserInfoService.getAllServerList();
        if(serverList.isEmpty()){
            log.error("{} serverList is empty", methodName);
            if (rollbackFunction != null) {
                rollbackFunction.run();
            }
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_FOUND_SERVER.getMessage());
        }

        try{
            switch (methodName) {
                case "updateRanking" ->
                        serverList.forEach(server -> messageHandlerService.sendRankingUpdateMessage(server, guid, IntervalType.IntervalType_Refresh));
                case "updateSnapshot" ->
                        serverList.forEach(server -> messageHandlerService.sendRankingUpdateMessage(server, guid, IntervalType.IntervalType_Snapshot));
                case "updateRankingInit" ->
                        serverList.forEach(server -> messageHandlerService.sendRankingUpdateMessage(server, guid, IntervalType.IntervalType_Initialization));
                default -> serverList.forEach(messageHandlerService::sendRankingScheduleMessage);
            }

        } catch (Exception e) {
            log.error("{} messageHandlerService error: {}", methodName, e.getMessage(), e);
            if (rollbackFunction != null) {
                rollbackFunction.run();
            }
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.MESSAGE_SEND_FAIL.getMessage());
        }
    }
}
