package com.caliverse.admin.scheduler.polling.service;

import com.caliverse.admin.domain.RabbitMq.MessageHandlerService;
import com.caliverse.admin.domain.RabbitMq.message.LanguageType;
import com.caliverse.admin.domain.RabbitMq.message.OperationSystemMessage;
import com.caliverse.admin.domain.entity.*;
import com.caliverse.admin.domain.entity.log.LogAction;
import com.caliverse.admin.domain.entity.log.LogStatus;
import com.caliverse.admin.domain.service.NoticeService;
import com.caliverse.admin.global.common.annotation.BusinessProcess;
import com.caliverse.admin.global.common.utils.CommonUtils;
import com.caliverse.admin.global.component.manager.BusinessProcessIdManager;
import com.caliverse.admin.mongodb.service.BusinessLogService;
import com.caliverse.admin.redis.service.RedisUserInfoService;
import com.caliverse.admin.scheduler.CommonScheduler;
import com.caliverse.admin.scheduler.ScheduleService;
import com.caliverse.admin.scheduler.entity.SchedulerType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Component
public class NoticeScheduler extends CommonScheduler {
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    private final NoticeService noticeService;
    private final RedisUserInfoService redisUserInfoService;
    private final ScheduleService schedulerService;
    private final MessageHandlerService messageHandlerService;

    public NoticeScheduler(NoticeService noticeService,
                           RedisUserInfoService redisUserInfoService,
                           ScheduleService schedulerService,
                           MessageHandlerService messageHandlerService,
                           BusinessLogService businessLogService,
                           BusinessProcessIdManager processIdManager) {
        super(businessLogService, processIdManager);
        this.noticeService = noticeService;
        this.redisUserInfoService = redisUserInfoService;
        this.schedulerService = schedulerService;
        this.messageHandlerService = messageHandlerService;
    }

    @Override
    protected void executeInternal() {
        executeWithProcessId(LogAction.SCHEDULE_NOTICE, this::executeLogic);
    }

    private void executeLogic(){
        List<InGame> noticeList = noticeService.getScheduleNoticeList();

        if(noticeList.isEmpty()) return;

        AtomicInteger processedCount = new AtomicInteger(0);
        AtomicInteger oneTimeCount = new AtomicInteger(0);
        AtomicInteger repeatCount = new AtomicInteger(0);
        AtomicInteger timeTypeCount = new AtomicInteger(0);
        AtomicInteger failedCount = new AtomicInteger(0);

        LocalDateTime startTime = LocalDateTime.now();

        try{
            noticeList.forEach(notice -> {
                String key = "notice-" + notice.getId();
                if (!schedulerService.isTaskExist(key)) {
                    processedCount.incrementAndGet();

                    Runnable taskRunner = new Runnable() {
                        private int count = notice.getSendCnt().intValue();
                        private final int repeat_cnt = notice.getRepeatCnt().intValue();
                        private final boolean isRepeat = notice.getIsRepeat();
                        private final long id = notice.getId();
                        private final String runKey = key;
                        private final InGame.REPEATTYPE repeatType = notice.getRepeatType();
                        private final LocalDateTime endDate = notice.getEndDt();

                        @Override
                        public void run() {
                            log.info("noticeJob noticeSchedule schedule run : {}", notice);

                            boolean is_send = noticeSend(notice);
                            if(!is_send) return;
                            noticeService.updateNoticeCount(id);

                            // 반복여부
                            if(isRepeat){
                                // 반복타입
                                switch(repeatType){
                                    // 횟수
                                    case COUNT -> {
                                        count++;
                                        // 정해진 횟수만큼 돌았으면 종료
                                        if(count >= repeat_cnt){
                                            noticeService.updateNoticeStatus(id, InGame.SENDSTATUS.FINISH);
                                            schedulerService.closeTask(runKey);
                                            log.info("Notice Schedule End RepeatType: Count, id: {}", id);
                                        }
                                    }
                                    // 일자
                                    case DATE -> {
                                        if (LocalDateTime.now().isAfter(endDate)) {
                                            noticeService.updateNoticeStatus(id, InGame.SENDSTATUS.FINISH);
                                            schedulerService.closeTask(runKey);
                                            log.info("Notice Schedule End RepeatType: Date, id: {}", id);
                                        }
                                    }
                                }
                            }else{
                                // 스케줄 해제 및 상태 변경
                                noticeService.updateNoticeStatus(id, InGame.SENDSTATUS.FINISH);
                                schedulerService.closeTask(runKey);
                                log.info("Notice One Time Schedule End id: {}", id);
                            }
                        }
                    };

                    // 반복여부 체크
                    if(notice.getIsRepeat()){
                        repeatCount.incrementAndGet();

                        // 반복타입
                        switch(notice.getRepeatType()){
                            // 횟수
                            case COUNT,DATE ->{
                                // 시작일자 - 현재일자
                                LocalDateTime startDate = notice.getSendDt();
                                long initialDelay = Duration.between(LocalDateTime.now(), startDate).toMillis();    // 시작시간
                                long period = LocalTime.parse(notice.getRepeatDt()).toSecondOfDay();    // 반복주기

                                ScheduledFuture<?> schedule = scheduler.scheduleAtFixedRate(taskRunner, initialDelay, period, TimeUnit.MILLISECONDS);
                                schedulerService.addTask(key, schedule);
                                noticeService.updateNoticeStatus(notice.getId(), InGame.SENDSTATUS.RUNNING);
                            }
                            // 특정시간
                            case TIME -> {

                                LocalDateTime now_date = LocalDateTime.now();
                                LocalDateTime start_date = notice.getSendDt();

                                if(now_date.isAfter(start_date)) {
                                    LocalTime now_time = LocalTime.now();
                                    LocalTime repeat_time = CommonUtils.stringToTime(notice.getRepeatDt());
                                    LocalDateTime end_date = notice.getEndDt();
                                    InGame.SENDSTATUS status = notice.getSendStatus();
                                    Long id = notice.getId();

                                    timeTypeCount.incrementAndGet();

                                    if(status.equals(InGame.SENDSTATUS.WAIT)) noticeService.updateNoticeStatus(id, InGame.SENDSTATUS.RUNNING);

                                    if (now_time.equals(repeat_time)) {
                                        log.info("noticeJob noticeSchedule RepeatType: Time run : {}", notice);
                                        boolean is_send = noticeSend(notice);
                                        if (!is_send) {
                                            log.error("Notice Schedule Fail RepeatType: Time, id: {}", id);
                                        }
                                    }

                                    if(status.equals(InGame.SENDSTATUS.RUNNING) && (now_date.isBefore(end_date) || now_date.isEqual(end_date))){
                                        noticeService.updateNoticeStatus(id, InGame.SENDSTATUS.FINISH);
                                        log.info("Notice Schedule End RepeatType: Time, id: {}", id);
                                    }
                                }
                            }
                        }

                    }else{
                        oneTimeCount.incrementAndGet();
                        // 시작일자 - 현재일자
                        LocalDateTime startDate = notice.getSendDt();
                        long initialDelay = Duration.between(LocalDateTime.now(), startDate).toMillis();

                        if(initialDelay < 0) {
                            failedCount.incrementAndGet();
                            log.error("noticeJob noticeSchedule schedule add fail time over");
                            noticeService.updateNoticeStatus(notice.getId(), InGame.SENDSTATUS.FAIL);
                            return;
                        }

                        log.info("noticeJob noticeSchedule schedule add : {}", notice);
                        ScheduledFuture<?> schedule = scheduler.schedule(taskRunner, initialDelay, TimeUnit.MILLISECONDS);
                        schedulerService.addTask(key, schedule);
                    }
                }
            });

            if (oneTimeCount.get() > 0 || repeatCount.get() > 0 || timeTypeCount.get() > 0) {
                businessLogService.logScheduler(
                        LogStatus.SUCCESS,
                        String.format("공지사항 스케줄 처리 완료: 총 %d건 처리 (일회성: %d건, 반복: %d건, 특정시간: %d건, 실패: %d건)",
                                processedCount.get(), oneTimeCount.get(), repeatCount.get(), timeTypeCount.get(), failedCount.get()),
                        "NOTICE_SCHEDULER",
                        startTime,
                        LocalDateTime.now(),
                        Map.of(
                                "totalProcessed", processedCount.get(),
                                "oneTimeNotices", oneTimeCount.get(),
                                "repeatNotices", repeatCount.get(),
                                "timeTypeNotices", timeTypeCount.get(),
                                "failedNotices", failedCount.get(),
                                "totalNoticeCount", noticeList.size()
                        )
                );
            }
        }catch (Exception e){
            log.error("noticeSchedule Exception: {}", e.getMessage());
            throw e;
        }
    }

    // 공지사항 전송
    private boolean noticeSend(InGame notice){
        try{
            //redis 채널 및 인던 정보 가져오기
            List<String> serverList = redisUserInfoService.getAllServerList();
            if(serverList.isEmpty()){
                log.error("noticeJob noticeSend serverList is empty");
                noticeService.updateNoticeStatus(notice.getId(), InGame.SENDSTATUS.FAIL);
                return false;
            }

            //메시지 처리
            List<Message> msgList = noticeService.getNoticeMessageList(notice.getId());
            List<OperationSystemMessage> contentList = new ArrayList<>();
            List<OperationSystemMessage> senderList = new ArrayList<>();
            for(Message msg : Collections.unmodifiableList(msgList)){
                LanguageType lang = null;
                String langText = msg.getLanguage();
                String sender = null;

                if(langText.equals(LANGUAGETYPE.EN.toString())){
                    lang = LanguageType.LanguageType_en;
                    sender = "Administrator";
                }else if(langText.equals(LANGUAGETYPE.JA.toString())){
                    lang = LanguageType.LanguageType_ja;
                    sender = "アドミニストレーター";
                }else{
                    lang = LanguageType.LanguageType_ko;
                    sender = "시스템 관리자";
                }
                contentList.add(OperationSystemMessage.newBuilder()
                        .setLanguageType(lang)
                        .setText(CommonUtils.stringToByte(msg.getContent())).build());
                senderList.add(OperationSystemMessage.newBuilder()
                        .setLanguageType(lang)
                        .setText(CommonUtils.stringToByte(sender)).build());
            }

            log.info("Send Notice Message: {}, type: {}", contentList, notice.getMessageType());
            messageHandlerService.sendNoticeMessage(serverList, notice.getMessageType().toString(), contentList, senderList);
            log.info("noticeJob noticeSend completed");
            return true;
        }catch (Exception e){
            log.error("noticeSend Exception: {}", e.getMessage());
            noticeService.updateNoticeStatus(notice.getId(), InGame.SENDSTATUS.FAIL);
        }
        return false;
    }

    @Override
    public SchedulerType getSchedulerType() {
        return SchedulerType.NOTICE;
    }
}
