package com.bsmlab.dfx.agent.listener.service;

import com.bsmlab.dfx.agent.config.AgentConfigDto;
import com.bsmlab.dfx.agent.config.AgentConfigReader;
import com.bsmlab.dfx.agent.listener.dto.AckDto;
import com.bsmlab.dfx.agent.listener.dto.CommandDto;
import com.bsmlab.dfx.agent.listener.dto.ReceiveMessageDto;
import com.bsmlab.dfx.agent.support.MessageUtils;
import com.bsmlab.dfx.agent.support.exception.IllegalMessageException;
import com.bsmlab.dfx.agent.support.exception.InCompleteMessageException;
import com.bsmlab.dfx.agent.support.exception.NullMessageException;
import com.bsmlab.dfx.agent.task.dropbox.DropBoxSchedulerService;
import com.bsmlab.dfx.agent.task.dropbox.DropBoxService;
import com.bsmlab.dfx.agent.task.postman.PostmanSchedulerService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.Part;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;

@RequiredArgsConstructor
@Slf4j
@Service
public class ListenerService {
    private final DropBoxService dropBoxService;
    private final DropBoxSchedulerService dropBoxSchedulerService;
    private final AgentConfigReader agentConfigReader;
    private final PostmanSchedulerService postmanSchedulerService;

    /**
     * 메시지를 파싱한다.
     * 메시지의 recipientHostId가 myHostId 이거나 메시지의 라우팅 중 마지막 hostId가 myHostId 이면 수신처리 한다.
     * 그렇지 않은 경우 라우팅 다음 순번으로 메시지를 전송한다. (MessageUtils.transfer)
     * @param messageJsonString
     * @return
     */
    public AckDto receiveMessage(String messageJsonString) {
        AckDto ackDto = null;
        try {
            ReceiveMessageDto receiveMessageDto = MessageUtils.toReceiveMessageDto(messageJsonString);
            String lastRoutingHostId = receiveMessageDto.getRoutingHostList().get(receiveMessageDto.getRoutingHostList().size() - 1).getHostId();
            if(receiveMessageDto.getRecipientHostId().equals(this.agentConfigReader.getAgentConfigDto().getMyHostId()) || lastRoutingHostId.equals(this.agentConfigReader.getAgentConfigDto().getMyHostId())) {
                if(dropBoxService.isExistToday(receiveMessageDto)) {
                    ackDto = AckDto.builder().result(AckDto.ResultType.RECEIVE_FAIL).messageUuid(receiveMessageDto.getMessageUuid()).resultText("금일 전송한 메시지 중 중복된 UUID 가 존재합니다.").build();
                }
                else {
                    dropBoxService.add(receiveMessageDto);
                    ackDto = AckDto.builder().result(AckDto.ResultType.RECEIVE_SUCCESS).messageUuid(receiveMessageDto.getMessageUuid()).build();
                }
            }
            else {
                AckDto transfarAckDto = MessageUtils.transfer(this.agentConfigReader, receiveMessageDto);
                ackDto = AckDto.builder().result(transfarAckDto.getResult()).build();
            }
        } catch (IllegalMessageException | NullMessageException | InCompleteMessageException | JsonProcessingException e) {
            log.error("{}", e, e);
            ackDto = AckDto.builder().result(AckDto.ResultType.RECEIVE_FAIL).resultText(e.getLocalizedMessage()).build();
        }
        return ackDto;
    }

    public AckDto receiveFiles(Collection<Part> parts) {
        AckDto ackDto = null;
        try {
            if(parts == null || parts.size() < 2) {
                throw new IllegalMessageException("파일 전송은 파일의 메타데이터인 application/json 과 n개의 파일 본문인 File Multipart 를 합쳐서 전송하여야 합니다.");
            }
            else {
                Iterator<Part> iterator = parts.iterator();
                Part firstPart = iterator.next();
                if(firstPart.getContentType() == null || !firstPart.getContentType().contains("application/json")) {
                    throw new IllegalMessageException("Multipart 의 첫 번째 데이터는 application/json 이어야 합니다.");
                }
                while(iterator.hasNext()) {
                    Part filePart = iterator.next();
                    if(filePart.getSubmittedFileName() == null) {
                        throw new IllegalMessageException("Multipart 의 두 번째 데이터 및 그 이후 데이터는 File 이어야 합니다.");
                    }
                }
                firstPart = iterator.next();
                String messageJsonString = new String(firstPart.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
                ReceiveMessageDto receiveMessageDto = MessageUtils.toReceiveMessageDto(messageJsonString);
                // DropBox(수신설정) 가져오기
                AgentConfigDto.DropBox dropBox = agentConfigReader.getDropBox(receiveMessageDto.getRecipientDropBoxId());
                // DropBox 의 경로에 따라서 파일 저장 위치 설정
                String saveDirectoryRoot = dropBox.getSaveDirectoryRoot();
                Date today = new Date(System.currentTimeMillis());
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd/hh/mm/ss");
                String targetDirectoryString = saveDirectoryRoot + "/" + dateFormat.format(today);
                File targetDirectory = new File(targetDirectoryString);
                if(!targetDirectory.exists()) {
                    targetDirectory.mkdirs();
                }
                byte[] buff = new byte[4096];
                while(iterator.hasNext()) {
                    // 파일 수신 처리
                    Part part = iterator.next();
                    String fileName = part.getSubmittedFileName();
                    String targetFilePath = targetDirectoryString + "/" + fileName;
                    Path targetPath = Paths.get(targetDirectoryString, fileName);
                    try(InputStream inputStream = part.getInputStream()) {
                        Files.copy(inputStream, targetPath, StandardCopyOption.REPLACE_EXISTING);
                    }
                    receiveMessageDto.getAttachFileList().add(targetFilePath);
                }
                // 수신 정보를 queue 에 저장하고 Ack 설정
                dropBoxService.add(receiveMessageDto);
                ackDto = AckDto.builder().result(AckDto.ResultType.RECEIVE_SUCCESS).build();
            }
        }
        catch(IllegalMessageException | IOException | InCompleteMessageException | NullMessageException e) {
            log.error("{}", e, e);
            ackDto = AckDto.builder().result(AckDto.ResultType.RECEIVE_FAIL).resultText(e.getLocalizedMessage()).build();
        }
        return ackDto;
    }

    /**
     * 상대 에이전트의 DropBox가 처리한 결과를 수신하고 수신한 AckDto에 따라서 postmanSchedulerService.makeCompleteSentMessage를 수행한다.
     * @param messageJsonString
     * @return
     */
    public AckDto receiveAck(String messageJsonString) {
        AckDto ackDto;
        try {
            ackDto = MessageUtils.toAckDto(messageJsonString);
            log.info("receiveAck: {}", ackDto);
            postmanSchedulerService.makeCompleteSentMessage(ackDto); // 상대측 Dropbox 처리 결과에 따라서 보낸 메시지 결과 처리
            if(agentConfigReader.isConnectedConsole()) {
                MessageUtils.announceMessageAck(this.agentConfigReader, ackDto);
            }
        }
        catch (NullMessageException | IllegalMessageException | JsonProcessingException e) {
            ackDto = AckDto.builder().result(AckDto.ResultType.RECEIVE_FAIL).resultText(e.getLocalizedMessage()).messageUuid("").build();
        }
        return ackDto;
    }

    public AckDto receiveCommand(String messageJsonString) {
        AckDto ackDto;
        CommandDto commandDto;
        try {
            commandDto = MessageUtils.toCommandDto(messageJsonString);
            String resultText = null;
            if(CommandDto.CommandType.ALIVE == commandDto.getCommandType()) { // ALIVE 메시지인 경우 "ALIVE" 문자열 반송
                resultText = "ALIVE";
            }
            else if(CommandDto.CommandType.INFORMATION == commandDto.getCommandType()) { // INFORMATION 메시지인 경우 AgentConfigDto의 인스턴스 내용을 반송
                AgentConfigDto agentConfigDto = this.agentConfigReader.getAgentConfigDto();
                ObjectMapper objectMapper = new ObjectMapper();
                try {
                    resultText = objectMapper.writeValueAsString(agentConfigDto);
                } catch (JsonProcessingException e) {
                    resultText = "";
                }
            }
            else if(CommandDto.CommandType.SAVE_CONFIG == commandDto.getCommandType()) { // SAVE_CONFIG 메시지인 경우 수신 데이터를 AgentConfigDto로 변환
                //TODO 1. Postman, Dropbox 쓰레드 정지
                this.postmanSchedulerService.stopAll();
                this.dropBoxSchedulerService.stopAll();
                //TODO 2. AgentConfigDto 갱신 및 settings.json으로 저장
                //TODO 3. Postman, Dropbox 쓰래드 시작
                this.dropBoxSchedulerService.launch();
                this.postmanSchedulerService.launch();

            }
            ackDto = AckDto.builder().result(AckDto.ResultType.PROCESS_SUCCESS).messageUuid(commandDto.getMessageUuid()).resultText(resultText).build();
        }
        catch (IllegalMessageException | NullMessageException e) {
            ackDto = AckDto.builder().result(AckDto.ResultType.RECEIVE_FAIL).resultText(e.getLocalizedMessage()).messageUuid("").build();
        }
        return ackDto;
    }
}
