package com.ease.gsms.server.services.dispatch;

import com.ease.gsms.server.model.Dispatch;
import com.ease.gsms.server.model.Message;
import com.ease.gsms.server.model.MessageStatus;
import com.ease.gsms.server.repositories.DispatchRepository;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

public abstract class MessageSender {

    private String id;

    private String user;

    private volatile Allocation allocation;

    private transient DispatchRepository repository;

    private transient WorkAllocator allocator;

    protected MessageSender(String id, String user) {
        this.id = id;
        this.user = user;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public void setRepository(DispatchRepository repository) {
        this.repository = repository;
    }


    public void setAllocator(WorkAllocator allocator) {
        this.allocator = allocator;
    }

    public void allocate(Allocation allocation) {

        if (!allocation.equals(this.allocation)) { // new allocation

            this.allocation = allocation;

            announceAllocation(allocation);

        }

    }


    public void deallocate() {

        announceDeallocation(this.allocation);

        this.allocation = null;

    }

    public abstract void announceAllocation(Allocation allocation);

    public abstract void announceDeallocation(Allocation allocation);

    public abstract void sendMessage(Message message);

    private ExecutorService messageSender = Executors.newSingleThreadExecutor();

    private void sendNextMessage() {

        Task task = allocation.current();

        if (task.hasNext()) {

            messageSender.submit(() -> {
                Integer pauseBetweenMessagesSeconds = task.getDispatch().getPauseBetweenMessagesSeconds();
                if (pauseBetweenMessagesSeconds > 0) {
                    try {
                        Thread.sleep(pauseBetweenMessagesSeconds * 1000);
                    } catch (InterruptedException ignored) {
                    }
                }
                sendMessage(task.next());
            });

        } else {

            // we're done with the task

            if (allocation.hasNext()) {

                allocation.next(); // move on to next task

                sendNextMessage();

            } else {

                // we're done with the entire allocation

                // TODO: do something when the entire allocation has been exhausted ?

            }

        }

    }

    public void allocationConfirmed(UUID allocationId) {

        if (allocation.getUuid().equals(allocationId)) {

            List<Long> allocatedMessageIds = new LinkedList<>();

            Date allocationDate = new Date();

            String iccid = allocation.getWorker().getId();

            for (Task task : allocation.getTasks()) {

                for (Message message : task.getMessages()) {

                    MessageStatus existingStatus = message.getStatus();

                    message.setFromICCID(iccid);

                    message.setAllocationDate(allocationDate);

                    task.getDispatch().getDispatchStatusHolder().updateMessageStatus(existingStatus, message.getStatus());

                    allocatedMessageIds.add(message.getId());

                }

            }

            repository.allocateMessages(allocatedMessageIds, iccid, allocationDate);

            if (allocation.hasNext()) {

                allocation.next();

                sendNextMessage();

            }

        }

    }

    public void messageSent(
            UUID allocationId,
            Long dispatchId,
            Long messageId,
            Integer sendingStatusId,
            Date sentDate
    ) {

        // update message, whatever the allocation might be

        Map<Long, Dispatch> dispatchMap = allocator.getWorkContainer().get(user);

        Dispatch dispatch = dispatchMap.get(dispatchId);

        if (dispatch != null) { // live dispatch

            Map<Long, Message> messagesById = dispatch.getMessagesById();

            if (messagesById != null) {

                Message message = messagesById.get(messageId);

                if (message != null) {

                    MessageStatus existingStatus = message.getStatus();

                    message.setFromICCID(id);

                    message.setSentDate(sentDate);

                    message.setSendingStatus(sendingStatusId);

                    repository.updateMessageSentInformation(
                            messageId,
                            id,
                            sentDate,
                            sendingStatusId
                    );

                    DispatchStatusHolder dispatchStatusHolder = dispatch.getDispatchStatusHolder();

                    if (dispatchStatusHolder != null) {

                        dispatchStatusHolder.updateMessageStatus(existingStatus, message.getStatus());

                    }

                }

            }

            if (allocation != null && allocation.getUuid().equals(allocationId)) { // message matches current allocation

                if (allocation.hasNext() || allocation.current().hasNext()) {

                    sendNextMessage();

                }

            }

        } else { // dispatch is not live anymore, but we update the db anyway

            Message message = repository.getMessageByIdForUser(messageId, user);

            if (message != null) {

                repository.updateMessageSentInformation(
                        messageId, id, sentDate, sendingStatusId
                );

            }

        }


    }

    public void messageDelivered(UUID allocation, Long dispatchId, Long messageId, Date deliveryDate, Integer deliveryStatusId) {

        // delivery reports might come in late with no live dispatches available

        Map<Long, Dispatch> dispatchMap = allocator.getWorkContainer().get(user);

        Dispatch dispatch = dispatchMap.get(dispatchId);

        if (dispatch != null) {

            Map<Long, Message> messagesById = dispatch.getMessagesById();

            if (messagesById != null) {

                Message message = messagesById.get(messageId);

                if (message != null) {

                    MessageStatus existingStatus = message.getStatus();

                    message.setDeliveredDate(deliveryDate);

                    message.setDeliveryStatus(deliveryStatusId);

                    repository.updateMessageDeliveryInformation(
                            messageId,
                            deliveryDate,
                            deliveryStatusId
                    );

                    DispatchStatusHolder dispatchStatusHolder = dispatch.getDispatchStatusHolder();

                    if (dispatchStatusHolder != null) {

                        dispatchStatusHolder.updateMessageStatus(existingStatus, message.getStatus());

                    }

                }

            }

        } else { // we're not live anymore

            Message message = repository.getMessageByIdForUser(messageId, user);

            if (message != null) {

                repository.updateMessageDeliveryInformation(
                        messageId, deliveryDate, deliveryStatusId
                );

            }

        }

    }

}
