package com.priusis.client.service;

import com.priusis.client.service.conf.PcPersistenceConfiguration;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.PostConstruct;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;

@Slf4j
public class PersistentFileServiceImpl implements PersistentFileService {

    private static final String STORAGE_FILE_PREFIX = "pc-storage-";
    private PcPersistenceConfiguration persistence;
    private ConcurrentLinkedDeque<MqttPersistentMessage> sendBuffer;
    private ConcurrentLinkedDeque<MqttPersistentMessage> resendBuffer;

    private ConcurrentMap<UUID, MqttCallbackWrapper> callbacks;
    private Map<UUID, MqttDeliveryFuture> futures;

    private File storageDir;

    @PostConstruct
    public void init() {
        callbacks = new ConcurrentHashMap<>();
        futures = new ConcurrentHashMap<>();
        initStorageDir();
        initBuffers();
    }

    private void initStorageDir() {
        storageDir = new File(persistence.getPath());
        if (!storageDir.exists()) {
            storageDir.mkdirs();
        }
    }

    private void initBuffers() {
        sendBuffer = new ConcurrentLinkedDeque<>();
        resendBuffer = new ConcurrentLinkedDeque<>();
    }

    @Override
    public MqttDeliveryFuture persistMessage(String topic, int msgId, byte[] payload,
                                             Consumer<Void> onSuccess,
                                             Consumer<Throwable> onFailure) throws IOException {
        MqttPersistentMessage message = MqttPersistentMessage.builder().id(UUID.randomUUID())
                .topic(topic).messageId(msgId).payload(payload).build();
        MqttDeliveryFuture future = new MqttDeliveryFuture();
        addMessageToBuffer(message);
        callbacks.put(message.getId(), new MqttCallbackWrapper(onSuccess, onFailure));
        futures.put(message.getId(), future);
        return future;
    }

    @Override
    public List<MqttPersistentMessage> getPersistentMessages() throws IOException {
        return getMqttPersistentMessages(sendBuffer);
    }

    @Override
    public List<MqttPersistentMessage> getResendMessages() throws IOException {
        return getMqttPersistentMessages(resendBuffer);
    }

    private List<MqttPersistentMessage> getMqttPersistentMessages(ConcurrentLinkedDeque<MqttPersistentMessage> buffer) throws IOException {
        List<MqttPersistentMessage> messages = new ArrayList<>(buffer);
        buffer.clear();
        return messages;
    }

    @Override
    public void resolveFutureSuccess(UUID id) {
        callbacks.remove(id);
        MqttDeliveryFuture future = futures.remove(id);
        if (future != null) {
            future.complete(Boolean.TRUE);
        }
    }

    @Override
    public void resolveFutureFailed(UUID id, Throwable e) {
        MqttDeliveryFuture future = futures.remove(id);
        if (future != null) {
            future.completeExceptionally(e);
        }
    }

    @Override
    public Optional<MqttDeliveryFuture> getMqttDeliveryFuture(UUID id) {
        return Optional.of(futures.get(id));
    }

    @Override
    public boolean deleteMqttDeliveryFuture(UUID id) {
        return futures.remove(id) != null;
    }

    @Override
    public Optional<Consumer<Void>> getSuccessCallback(UUID id) {
        MqttCallbackWrapper mqttCallbackWrapper = callbacks.get(id);
        if (mqttCallbackWrapper == null) {
            return Optional.empty();
        }
        return Optional.ofNullable(mqttCallbackWrapper.getSuccessCallback());
    }

    @Override
    public Optional<Consumer<Throwable>> getFailureCallback(UUID id) {
        MqttCallbackWrapper mqttCallbackWrapper = callbacks.get(id);
        if (mqttCallbackWrapper == null) {
            return Optional.empty();
        }
        return Optional.ofNullable(mqttCallbackWrapper.getFailureCallback());
    }

    @Override
    public void saveForResend(MqttPersistentMessage message) throws IOException {
        if (resendBuffer.size() >= persistence.getBufferSize()) {
            log.info("Resend message too long gt bufferSize [{}]", sendBuffer.size());
            //resendFileCounter = getFileCounter(resendFiles);
            //resendFiles.add(flushBufferToFile(resendBuffer, RESEND_FILE_PREFIX + resendFileCounter));
        }
        resendBuffer.add(message);
    }

    @Override
    public void saveForResend(List<MqttPersistentMessage> messages) throws IOException {
        for (MqttPersistentMessage message : messages) {
            saveForResend(message);
        }
    }

    @Override
    public File flushRpcDataToFile(MqttRpcDataMessage mqttRpcDataMessage) throws IOException {
        ObjectOutputStream outStream = null;
        try {
            File newFile = new File(storageDir, STORAGE_FILE_PREFIX + mqttRpcDataMessage.getMethod());
            outStream = new ObjectOutputStream(new FileOutputStream(newFile));
            outStream.writeObject(mqttRpcDataMessage);
            return newFile;
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw e;
        } finally {
            try {
                if (outStream != null)
                    outStream.close();
            } catch (IOException e) {
                log.error(e.getMessage(), e);
                throw e;
            }
        }
    }

    @Override
    public MqttRpcDataMessage readFromFile(String method) throws IOException {
        File file = new File(storageDir, STORAGE_FILE_PREFIX + method);
        ObjectInputStream inputStream = null;
        MqttRpcDataMessage result = null;
        try {
            inputStream = new ObjectInputStream(new FileInputStream(file));
            result = (MqttRpcDataMessage) inputStream.readObject();
        } catch (EOFException e) {
            return result;
        } catch (ClassNotFoundException e) {
            log.error(e.getMessage(), e);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw e;
        } finally {
            try {
                if (inputStream != null)
                    inputStream.close();
            } catch (IOException e) {
                log.error(e.getMessage(), e);
                throw e;
            }
        }
        return result;
    }

    private void addMessageToBuffer(MqttPersistentMessage message) throws IOException {
        if (sendBuffer.size() >= persistence.getBufferSize()) {
            log.info("Send message too long gt bufferSize [{}]", sendBuffer.size());
            //storageFiles.add(flushBufferToFile(sendBuffer, STORAGE_FILE_PREFIX + storageFileCounter++));
        }
        sendBuffer.add(message);
    }

    public void setPersistence(PcPersistenceConfiguration persistence) {
        this.persistence = persistence;
    }
}
