package com.kospo.talk.config.listener;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.kospo.talk.config.utils.CommonUtils;
import com.kospo.talk.model.*;
import com.kospo.talk.payload.MessageDto;
import com.kospo.talk.payload.TalkDto;
import com.kospo.talk.payload.TalkMemberDto;
import com.kospo.talk.service.*;
import jakarta.annotation.PreDestroy;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.Message;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.*;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

@Slf4j
@Component
public class RabbitMqMessageListener {
    @Value("${server.attach}")
    private String attachPath;
    @Value("${rabbitmq.user.exchange}")
    private String userExchange;
    @Value("${rabbitmq.user.route}")
    private String userRoute;
    @Autowired
    private TalkMessageService talkMessageService;
    @Autowired
    private TalkService talkService;
    @Autowired
    private TalkMemberService talkMemberService;
    @Autowired
    private TalkAttachService talkAttachService;
    @Autowired
    private TalkReadService talkReadService;
    @Autowired
    private RabbitTemplate userTemplate;

    ArrayList<TalkMessage> talkMessages = new ArrayList<>();
    ArrayList<TalkRead> talkReads = new ArrayList<>();

    private final ExecutorService executorService = Executors.newFixedThreadPool(10);

    @Scheduled(fixedRate = 300)
    public void CollectSave() {
        if (talkMessages.isEmpty()) {
            return;
        }

        if (!talkMessages.isEmpty()) {
//            System.out.println("in");
            ArrayList<TalkMessage> tempMessages = (ArrayList<TalkMessage>) talkMessages.clone();
            ArrayList<TalkRead> tempReads = (ArrayList<TalkRead>) talkReads.clone();
            talkMessageService.saveAll(tempMessages);
            talkReadService.saveAll(tempReads);
            talkMessages.removeAll(tempMessages);
            talkReads.removeAll(tempReads);
        }
    }

    @Transactional
    @RabbitListener(queues = "${rabbitmq.talk.queue}")
    public void receive(Message message) throws IOException {
        try {
            SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.wrap(message);
            String sessionId = headerAccessor.getSessionId();
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.registerModule(new JavaTimeModule());
            TalkDto talkDto = objectMapper.readValue((byte[]) message.getPayload(), TalkDto.class);
//            System.out.println("**************************************** type : " + talkDto.getType() + " insSabun : " + talkDto.getInsSabun() + " ****************************************");
            List<String> ignores = Arrays.asList("[사진]", "[파일]", "[매뉴얼]");
            if (talkDto.getType().equals("MESSAGE")) {
                Talk dto = talkService.findByOne(talkDto.getTalkId());
                if (dto != null) {
                    AtomicBoolean searchYn = new AtomicBoolean(true);
                    ignores.stream().forEach((ignore) -> {
                        if (talkDto.getMessage().indexOf(ignore) > -1) searchYn.set(false);
                        if (talkDto.getInsSabun().equals("info")) searchYn.set(false);
                    });

                    talkMessages.add(TalkMessage.builder()
                            .id(TalkMessageId.builder().talkId(talkDto.getTalkId()).sabun(talkDto.getInsSabun()).insDate(talkDto.getInsDate()).build())
                            .message(talkDto.getMessage())
                            .searchYn(searchYn.get())
                            .insName(talkDto.getInsName()).build());

                    List<TalkMember> members = talkMemberService.findAllByTalkId(talkDto.getTalkId());
                    members.stream().forEach((mem) -> {
                        if (!mem.getId().getSabun().equals("info")) {
                            talkReads.add(TalkRead.builder()
                                    .readYn(talkDto.getInsSabun().equals(mem.getId().getSabun()) ? true : false)
                                    .id(TalkReadId.builder()
                                            .talkId(talkDto.getTalkId())
                                            .sabun(mem.getId().getSabun())
                                            .insDate(talkDto.getInsDate()).build()).build());
                        }
                    });

                    if (!talkDto.getMessage().isEmpty() || !talkDto.getMessage().isBlank()) {
                        if (talkDto.getMessage().contains("[매뉴얼]")) {
                            dto.setLastMessage("[매뉴얼]");
                        } else {
                            dto.setLastMessage(talkDto.getMessage());
                        }
                        dto.setLastMessageDate(talkDto.getInsDate());
                        if (talkDto.getStartYn() != null) dto.setStartYn(talkDto.getStartYn());
                        talkService.createTalk(dto);
                    }

                }
            } else if (talkDto.getType().equals("QUESTION")) {
                Talk dto = talkService.findByOne(talkDto.getTalkId());
                if (dto != null) {
                    talkMessages.add(TalkMessage.builder()
                            .id(TalkMessageId.builder().talkId(talkDto.getTalkId()).sabun(talkDto.getInsSabun()).insDate(talkDto.getInsDate()).build())
                            .message(talkDto.getMessage())
                            .searchYn(false)
                            .insName(talkDto.getInsName()).build());
                    List<TalkMember> members = talkMemberService.findAllByTalkId(talkDto.getTalkId());
                    members.stream().forEach((mem) -> {
                        if (!mem.getId().getSabun().equals("info")) {
                            talkReads.add(TalkRead.builder()
                                    .readYn(talkDto.getInsSabun().equals(mem.getId().getSabun()) ? true : false)
                                    .id(TalkReadId.builder()
                                            .talkId(talkDto.getTalkId())
                                            .sabun(mem.getId().getSabun())
                                            .insDate(talkDto.getInsDate()).build()).build());
                        }
                    });
                }
            } else if (talkDto.getType().equals("CLOSE")) {
                Talk talk = talkService.findByOne(talkDto.getTalkId());
                talk.setCloseYn(true);
                talk.setRatingYn(false);
                if (talkDto.getForce()) {
                    talk.setRatingYn(true);
                    talk.setCompYn(true);
                }
                talkService.createTalk(talk);
            } else if (talkDto.getType().equals("READ")) {
                talkMessageService.read(talkDto.getTalkId(), talkDto.getInsSabun());
            } else if (talkDto.getType().equals("ATTACH_INIT")) {
//                System.out.println("listener ATTACH_INIT");

                Talk dto = talkService.findByOne(talkDto.getTalkId());
                talkMessageService.save(TalkMessage.builder()
                        .id(
                                TalkMessageId.builder().talkId(talkDto.getTalkId()).sabun(talkDto.getInsSabun()).insDate(talkDto.getInsDate()).build()
                        )
                        .message(talkDto.getMessage())
                        .searchYn(false)
                        .insName(talkDto.getInsName()).build());

                dto.setLastMessage(talkDto.getMessage());
                dto.setLastMessageDate(talkDto.getInsDate());
                talkService.createTalk(dto);
            }
        } catch (Exception e) {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.registerModule(new JavaTimeModule());
            TalkDto talkDto = objectMapper.readValue((byte[]) message.getPayload(), TalkDto.class);
            log.error(talkDto.toString());
            throw new AmqpRejectAndDontRequeueException(e);
        }
    }

    @RabbitListener(queues = "${rabbitmq.file.queue}")
    public void receiveFile(Message message) throws IOException {
        try {
            SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.wrap(message);
            String sessionId = headerAccessor.getSessionId();
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.registerModule(new JavaTimeModule());
            MessageDto messageDto = objectMapper.readValue((byte[]) message.getPayload(), MessageDto.class);
//            System.out.println("**************************************** file type : " + messageDto.getType() + " insSabun : " + messageDto.getInsSabun() + " ****************************************");

            if (messageDto.getType().equals("ATTACH_START")) {
//                System.out.println("listener ATTACH_START");
                String vnm = messageDto.getAttach().getVnm();
//                System.out.println(vnm);
                TalkAttach attach = TalkAttach.builder()
                        .id(TalkAttachId.builder()
                                .talkId(messageDto.getTalkId())
                                .sort(messageDto.getAttach().getSort())
                                .sabun(messageDto.getInsSabun())
                                .insDate(messageDto.getInsDate())
                                .build()
                        )
                        .onm(messageDto.getAttach().getOnm())
                        .vnm(vnm)
                        .path(attachPath)
                        .type(messageDto.getAttach().getType())
                        .size(messageDto.getAttach().getSize())
                        .uploading(true)
                        .build();

                talkAttachService.save(attach);
            } else if (messageDto.getType().equals("ATTACH_PROGRESS")) {
//                System.out.println("listener ATTACH_PROGRESS");
                uploadAndResume(messageDto.getAttach().getVnm(), messageDto.getAttach().getData());
            } else if (messageDto.getType().equals("ATTACH_END")) {
//                System.out.println("listener ATTACH_end");
                talkAttachService.getTalkAttachList(messageDto.getTalkId(), messageDto.getInsSabun(), messageDto.getInsDate()).stream().forEach((attach) -> {
                    attach.setUploading(false);
                    talkAttachService.save(attach);
                });



            }
        } catch(Exception e) {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.registerModule(new JavaTimeModule());
            MessageDto messageDto = objectMapper.readValue((byte[]) message.getPayload(), MessageDto.class);
            log.error(messageDto.toString());
            throw new AmqpRejectAndDontRequeueException(e);
        }
    }

    public synchronized void uploadAndResume(String vnm, byte[] data) {
        File file = new File(attachPath, vnm);
        BufferedOutputStream bos = null;

        try {
            if (file.exists()) {
                bos = new BufferedOutputStream(new FileOutputStream(file, true));

            } else {
                bos = new BufferedOutputStream(new FileOutputStream(file));

            }
            bos.write(data);
            bos.flush();
            bos.close();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
    }

//    public synchronized void uploadAndResume(String vnm, byte[] data) {
//        File file = new File(attachPath, vnm);
//        System.out.println(file.getAbsoluteFile());
//        BufferedOutputStream bos = null;
//        long skip = 0;
//
//        try {
//            if (file.exists()) {
//                bos = new BufferedOutputStream(new FileOutputStream(file, true));
//
//            } else {
//                bos = new BufferedOutputStream(new FileOutputStream(file));
//
//            }
//            bos.write(data);
//            bos.flush();
//        } catch (FileNotFoundException e) {
//            throw new RuntimeException(e);
//        } catch (IOException e) {
//            throw new RuntimeException(e);
//        } finally {
//            if(bos != null) {
//                try {
//                    bos.close();
//                } catch (IOException ex) {
//                    throw new RuntimeException(ex);
//                }
//            }
//        }
//    }


    @RabbitListener(queues = "${rabbitmq.user.queue}")
    public void adminReceive(Message message) throws IOException {
        try {
            SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.wrap(message);
            String sessionId = headerAccessor.getSessionId();
            String sabun = (String) headerAccessor.getHeader("sabun");
            String name = (String) headerAccessor.getHeader("name");
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.registerModule(new JavaTimeModule());
            TalkDto talkDto = objectMapper.readValue((byte[]) message.getPayload(), TalkDto.class);

            if (talkDto.getType().equals("JOIN")) {
                TalkDto dto = talkService.findByTalkId(talkDto.getTalkId());
                if (dto == null) {
                    talkService.createTalk(Talk.builder()
                            .talkId(talkDto.getTalkId())
                            .closeYn(false)
                            .startYn(false)
                            .compYn(false)
                            .workId(talkDto.getWorkId())
                            .insSabun(talkDto.getInsSabun())
                            .insDate(LocalDateTime.now())
                            .insName(talkDto.getInsName())
                            .build()
                    );

                }

                talkMemberService.save(
                        TalkMemberDto.builder()
                                .talkId(talkDto.getTalkId())
                                .sabun(sabun)
                                .name(name)
                                .markYn(false)
                                .deleteYn(false)
                                .dummyYn(false).build()
                );
            }

//            System.out.println("user received");
        } catch (Exception e) {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.registerModule(new JavaTimeModule());
            MessageDto messageDto = objectMapper.readValue((byte[]) message.getPayload(), MessageDto.class);
            log.error(messageDto.toString());
            throw new AmqpRejectAndDontRequeueException(e);
        }
    }


    @Transactional
    @RabbitListener(queues = "${rabbitmq.hub.queue}")
    public void hubChannel(Message message) throws IOException {
    }

    @Transactional
    @RabbitListener(queues = "${rabbitmq.work.queue}")
    public void workChannel(Message message) throws IOException {
    }

    @RabbitListener(queues = "${rabbitmq.dead.queue}")
    public void dadChannel(Message message) throws IOException {
        SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.wrap(message);
        String sessionId = headerAccessor.getSessionId();
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        MessageDto messageDto = objectMapper.readValue((byte[]) message.getPayload(), MessageDto.class);
        if(messageDto.getType().equals("MESSAGE") || messageDto.getType().contains("ATTACH")) {
            messageDto.setType("DEAD_MESSAGE");
            userTemplate.convertAndSend(userExchange, userRoute + messageDto.getInsSabun(), messageDto);
        }
    }
}
