package org.mercury_im.messenger.data.repository;

import org.mercury_im.messenger.data.mapping.DirectChatMapping;
import org.mercury_im.messenger.data.mapping.GroupChatMapping;
import org.mercury_im.messenger.data.mapping.MessageMapping;
import org.mercury_im.messenger.data.model.DirectChatModel;
import org.mercury_im.messenger.data.model.DirectMessagesRelation;
import org.mercury_im.messenger.data.model.GroupChatModel;
import org.mercury_im.messenger.data.model.GroupMessagesRelation;
import org.mercury_im.messenger.data.model.MessageModel;
import org.mercury_im.messenger.data.model.MessagePayloadContainerModel;
import org.mercury_im.messenger.data.model.MessagePayloadModel;
import org.mercury_im.messenger.data.repository.dao.DirectChatDao;
import org.mercury_im.messenger.data.repository.dao.GroupChatDao;
import org.mercury_im.messenger.data.repository.dao.MessageDao;
import org.mercury_im.messenger.entity.chat.DirectChat;
import org.mercury_im.messenger.entity.chat.GroupChat;
import org.mercury_im.messenger.entity.message.Message;
import org.mercury_im.messenger.util.ThreadUtils;

import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;

import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.Scheduler;
import io.reactivex.Single;
import io.requery.Persistable;
import io.requery.query.ResultDelegate;
import io.requery.reactivex.ReactiveEntityStore;

public class XmppMessageRepository
        extends RequeryRepository
        implements MessageRepository {

    private final MessageMapping messageMapping;
    private final DirectChatMapping directChatMapping;
    private final GroupChatMapping groupChatMapping;

    private final DirectChatDao directChatDao;
    private final GroupChatDao groupChatDao;

    private final MessageDao dao;


    @Inject
    public XmppMessageRepository(ReactiveEntityStore<Persistable> data,
                                 @Named(value = ThreadUtils.SCHEDULER_IO) Scheduler subscriberScheduler,
                                 @Named(value = ThreadUtils.SCHEDULER_UI) Scheduler observerScheduler,
                                 MessageMapping messageMapping,
                                 DirectChatMapping directChatMapping,
                                 GroupChatMapping groupChatMapping) {
        super(data, subscriberScheduler, observerScheduler);
        this.messageMapping = messageMapping;
        this.directChatMapping = directChatMapping;
        this.groupChatMapping = groupChatMapping;
        this.directChatDao = new DirectChatDao(data);
        this.groupChatDao = new GroupChatDao(data);
        this.dao = new MessageDao(data);
    }

    @Override
    public Single<Message> insertMessage(DirectChat chat, Message message) {
        return directChatDao.get(chat.getId()).maybe()
                .switchIfEmpty(directChatDao.insert(directChatMapping.toModel(chat)))
                .map(chatModel -> toRelation(chatModel, messageMapping.toModel(message)))
                .flatMap(data()::insert)
                .map(DirectMessagesRelation::getMessage)
                .map(messageModel -> messageMapping.toEntity(messageModel, message))
                .subscribeOn(subscriberScheduler())
                .observeOn(observerScheduler());
    }

    @Override
    public Single<Message> insertMessage(GroupChat chat, Message message) {
        return groupChatDao.get(chat.getId()).maybe()
                .switchIfEmpty(groupChatDao.insert(groupChatMapping.toModel(chat)))
                .map(chatModel -> toRelation(chatModel, messageMapping.toModel(message)))
                .flatMap(data()::insert)
                .map(GroupMessagesRelation::getMessage)
                .map(messageModel -> messageMapping.toEntity(messageModel, message))
                .subscribeOn(subscriberScheduler())
                .observeOn(observerScheduler());
    }

    @Override
    public Observable<List<Message>> observeMessages(DirectChat chat) {
        return data().select(MessageModel.class)
                .from(MessageModel.class)
                .join(DirectMessagesRelation.class)
                .on(DirectMessagesRelation.MESSAGE_ID.eq(MessageModel.ID))
                .get().observableResult()
                .map(ResultDelegate::toList)
                .map(this::messageModelsToEntities)
                .subscribeOn(subscriberScheduler())
                .observeOn(observerScheduler());
    }

    @Override
    public Observable<List<Message>> observeMessages(GroupChat chat) {
        return data().select(MessageModel.class)
                .from(MessageModel.class)
                .join(GroupMessagesRelation.class)
                .on(GroupMessagesRelation.MESSAGE_ID.eq(MessageModel.ID))
                .where(GroupMessagesRelation.CHAT_ID.eq(chat.getId()))
                .get().observableResult()
                .map(ResultDelegate::toList)
                .map(this::messageModelsToEntities)
                .subscribeOn(subscriberScheduler())
                .observeOn(observerScheduler());
    }

    @Override
    public Observable<List<Message>> findMessagesWithBody(String body) {
        return data().select(MessageModel.class)
                .from(MessageModel.class)
                .join(MessagePayloadContainerModel.class)
                .on(MessagePayloadContainerModel.MESSAGE_ID.eq(MessageModel.ID))
                .join(MessagePayloadModel.class)
                .on(MessagePayloadModel.PAYLOAD_CONTAINER_ID.eq(MessagePayloadContainerModel.ID))
                .where(MessagePayloadModel.BODY.eq(body))
                .get().observableResult()
                .map(ResultDelegate::toList)
                .map(this::messageModelsToEntities)
                .subscribeOn(subscriberScheduler())
                .observeOn(observerScheduler());
    }

    @Override
    public Observable<List<Message>> findMessagesWithBody(DirectChat chat, String body) {
        return data().select(MessageModel.class)

                .from(MessageModel.class)
                .join(DirectMessagesRelation.class)
                .on(DirectMessagesRelation.MESSAGE_ID.eq(MessageModel.ID))
                .join(MessagePayloadContainerModel.class)
                .on(MessagePayloadContainerModel.MESSAGE_ID.eq(MessageModel.ID))
                .join(MessagePayloadModel.class)
                .on(MessagePayloadModel.PAYLOAD_CONTAINER_ID.eq(MessagePayloadContainerModel.ID))

                .where(MessagePayloadModel.BODY.eq(body))
                .and(DirectMessagesRelation.CHAT_ID.eq(chat.getId()))
                .get().observableResult()
                .map(ResultDelegate::toList)
                .map(this::messageModelsToEntities)
                .subscribeOn(subscriberScheduler())
                .observeOn(observerScheduler());
    }

    @Override
    public Observable<List<Message>> findMessagesWithBody(GroupChat chat, String body) {
        return data().select(MessageModel.class)

                .from(MessageModel.class)
                .join(GroupMessagesRelation.class)
                .on(GroupMessagesRelation.MESSAGE_ID.eq(MessageModel.ID))
                .join(MessagePayloadContainerModel.class)
                .on(MessagePayloadContainerModel.MESSAGE_ID.eq(MessageModel.ID))
                .join(MessagePayloadModel.class)
                .on(MessagePayloadModel.PAYLOAD_CONTAINER_ID.eq(MessagePayloadContainerModel.ID))

                .where(MessagePayloadModel.BODY.eq(body))
                .and(GroupMessagesRelation.CHAT_ID.eq(chat.getId()))
                .get().observableResult()
                .map(ResultDelegate::toList)
                .map(this::messageModelsToEntities)
                .subscribeOn(subscriberScheduler())
                .observeOn(observerScheduler());
    }

    @Override
    public Single<Message> upsertMessage(DirectChat chat, Message message) {
        return null;
        /*
        return data().select(DirectMessagesRelation.class)
                .where(DirectMessagesRelation.CHAT_ID.eq(chat.getId()))
                .and(DirectMessagesRelation.MESSAGE_ID.eq(message.getId()))
                .get().maybe()
                .switchIfEmpty(Single.just(message)
                        .map(messageMapping::toEntity)
                        .flatMap(messageModel -> )
         */
    }

    @Override
    public Single<Message> upsertMessage(GroupChat chat, Message message) {
        return null;
    }

    @Override
    public Single<Message> updateMessage(Message message) {
        return dao.get(message.getId()).maybe().toSingle()
                .map(model -> messageMapping.toModel(message, model))
                .flatMap(data()::update)
                .map(model -> messageMapping.toEntity(model, message))
                .subscribeOn(subscriberScheduler())
                .observeOn(observerScheduler());
    }

    @Override
    public Completable deleteMessage(Message message) {
        return data().delete(MessageModel.class)
                .where(MessageModel.ID.eq(message.getId()))
                .get().single().ignoreElement();
    }

    private DirectMessagesRelation toRelation(DirectChatModel chat, MessageModel message) {
        DirectMessagesRelation relation = new DirectMessagesRelation();
        relation.setChat(chat);
        relation.setMessage(message);
        return relation;
    }

    private GroupMessagesRelation toRelation(GroupChatModel chat, MessageModel message) {
        GroupMessagesRelation relation = new GroupMessagesRelation();
        relation.setChat(chat);
        relation.setMessage(message);
        return relation;
    }

    private List<Message> messageModelsToEntities(List<MessageModel> models) {
        List<Message> entities = new ArrayList<>(models.size());
        for (MessageModel model : models) {
            entities.add(messageMapping.toEntity(model));
        }
        return entities;
    }
}
