package com.bsmlab.dfx.agent.task.dropbox;

import com.bsmlab.dfx.agent.config.AgentConfigReader;
import com.bsmlab.dfx.agent.listener.dto.ReceiveMessageDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

@Service
@RequiredArgsConstructor
@Slf4j
/**
 * Listener 가 메시지를 수신한 경우 DropBoxService 를 사용하여 파일 기록과 동시에 공유메모리(queue)에 파일 경로를 저장한다. (DropBoxService.add())
 * TaskExecutorStarter 에서 생성한 처리 쓰레드가 DropBoxService.poll() 을 호출하여 메시지 파일을 가져가서 처리한다.
 */
public class DropBoxService {
    private final AgentConfigReader agentConfigReader;
    private ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
    private boolean isAllowedToPoll;

    /**
     * 수신한 메시지(ReceiveMessageDto)는 하루 이내에 UUID가 중복되지 않아야 한다. 일일 UUID 중복을 체크한다.
     * @param receiveMessageDto
     * @return
     */
    public boolean isExistToday(ReceiveMessageDto receiveMessageDto) {
        boolean isExist = false;
        File root = new File(agentConfigReader.getAgentConfigDto().getDropBoxConfig().getReceivedMessageStorageRoot());
        Date today = new Date(System.currentTimeMillis());
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd/");
        String todayDirectoryString = root.getAbsolutePath() + "/" + dateFormat.format(today);
        DecimalFormat decimalFormat = new DecimalFormat("00");
        for(int i = 0; i < 24; i++) {
            File targetFilePath = new File(todayDirectoryString + decimalFormat.format(i) + "/" + receiveMessageDto.getMessageUuid());
            isExist = targetFilePath.exists();
            if(isExist) {
                break;
            }
        }
        return isExist;
    }

    /**
     * 수신한 메시지를 파일로 저장하고 공유메모리(queue)에 파일 경로를 등록한다.
     * @param receiveMessageDto 수신한 메지시의 인스턴스
     */
    public void add(ReceiveMessageDto receiveMessageDto) {
        File root = new File(agentConfigReader.getAgentConfigDto().getDropBoxConfig().getReceivedMessageStorageRoot());
        Date today = new Date(System.currentTimeMillis());
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd/HH");
        String targetDirectoryString = root.getAbsolutePath() + "/" + dateFormat.format(today);
        File targetDirectory = new File(targetDirectoryString);
        if(!targetDirectory.exists()) {
            targetDirectory.mkdirs();
        }
        String targetFilePath = targetDirectoryString + "/" + receiveMessageDto.getMessageUuid();
        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(targetFilePath)))) {
            objectOutputStream.writeObject(receiveMessageDto);
            this.queue.add(targetFilePath);
        }
        catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 기동 시 호출함. 수신하였으나 처리하지 않은 메시지 파일을 찾아서 처리 queue 에 넣는다.
     */
    public int addNotProcessedMessageFile() {
        File root = new File(agentConfigReader.getAgentConfigDto().getDropBoxConfig().getReceivedMessageStorageRoot());
        List<File> fileList = new ArrayList<>();
        this.findAndAddMessageFile(root, fileList);
        for(File file : fileList) {
            this.queue.add(file.getAbsolutePath());
        }
        return fileList.size();
    }

    private void findAndAddMessageFile(File parentDirectory, List<File> fileList) {
        File[] filesOnDirectory = parentDirectory.listFiles();
        if(filesOnDirectory != null) {
            for(File file : filesOnDirectory) {
                if(file.canRead() && !".".equals(file.getName()) && !"..".equals(file.getName())) {
                    if(file.isFile()) {
                        if(!this.queue.contains(file.getAbsolutePath())) {
                            fileList.add(file);
                        }
                    }
                    else if(file.isDirectory()) {
                        findAndAddMessageFile(file, fileList);
                    }
                }
            }
        }
    }

    /**
     * 공유메모리(queue)의 파일 경로를 획득한다.
     * @return ReceiveMessageDto의 serialize 파일 경로
     */
    public String poll() {
        return this.isAllowedToPoll ? queue.poll() : null;
    }

    public void denyToPoll() {
        this.isAllowedToPoll = false;
    }

    public void allowToPoll() {
        this.isAllowedToPoll = true;
    }

    public int getPollSize() {
        return queue.size();
    }

    public void moveMessageFile(String messageFilePath, boolean isSuccess) throws IOException {
        File root = null;
        if(isSuccess) {
            root = new File(agentConfigReader.getAgentConfigDto().getDropBoxConfig().getProcessedMessageStorageRoot());
        }
        else {
            root = new File(agentConfigReader.getAgentConfigDto().getDropBoxConfig().getFailureMessageStorageRoot());
        }
        Date today = new Date(System.currentTimeMillis());
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd/HH");
        String targetDirectoryString = root.getAbsolutePath() + "/" + dateFormat.format(today);
        File targetDirectory = new File(targetDirectoryString);
        if(!targetDirectory.exists()) {
            targetDirectory.mkdirs();
        }
        File messageFile = new File(messageFilePath);
        Path source = messageFile.toPath();
        Path target = Paths.get(targetDirectoryString, messageFile.getName());
        Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
        log.info("a message file moved. from [{}] to [{}]", messageFilePath, target.toAbsolutePath());
    }

    public void clearMessageFile(String messageFilePath) {
        File messageFile = new File(messageFilePath);
        if(messageFile.exists()) {
            messageFile.delete();
        }
        log.info("a message file cleared. [{}]", messageFilePath);
    }

    public void scanManualMessageFile(int maxFileCount) throws IOException {
        File manualMessageStorageRootFile = new File(this.agentConfigReader.getAgentConfigDto().getDropBoxConfig().getManualMessageStorageRoot());
        List<File> fileList = new ArrayList<>();
        this.scanAllFile(manualMessageStorageRootFile, fileList);
        int counter = Math.min(fileList.size(), 100);
        for(int i = 0; i < counter; i++) {
            try (ObjectInputStream objectInputStream = new ObjectInputStream(new BufferedInputStream(new FileInputStream(fileList.get(i))))) {
                ReceiveMessageDto receiveMessageDto = (ReceiveMessageDto)objectInputStream.readObject(); objectInputStream.readObject();
                this.queue.add(fileList.get(i).getAbsolutePath());
                fileList.get(i);
                // move receive
            } catch (FileNotFoundException e) {
                // do nothing
            } catch (ClassNotFoundException e) {
                // failure 이동
                this.moveMessageFile(fileList.get(i).getAbsolutePath(), false);
            }
        }
    }

    private void scanAllFile(File root, List<File> foundFileList) {
        if(root == null) {
            return;
        }
        File[] fileArray = root.listFiles();
        if(fileArray != null) {
            for(File file : fileArray) {
                if(file.isFile()) {
                    long firstSize = file.length();
                    try {
                        Thread.sleep(10);
                    }
                    catch(InterruptedException e) {
                        // do nothing
                    }
                    long secondSize = file.length();
                    if(firstSize != secondSize) { // 파일 복사 등 저장 중인 파일
                        continue;
                    }
                    foundFileList.add(file);
                }
                else if(file.isDirectory()) {
                    this.scanAllFile(file, foundFileList);
                }
            }
        }
    }
}
