package de.beyondsoft.ownchat.data.repository;

import de.beyondsoft.ownchat.data.repository.mapper.Mapper;
import de.beyondsoft.ownchat.data.repository.models.MessageRealm;
import de.beyondsoft.ownchat.data.repository.specifications.MessageByIdSpecification;
import de.beyondsoft.ownchat.data.repository.specifications.RealmSpecification;
import de.beyondsoft.ownchat.data.repository.specifications.Specification;
import de.beyondsoft.ownchat.model.Message;

import java.util.List;
import java.util.concurrent.Callable;

import io.realm.Sort;
import rx.Completable;
import rx.Observable;
import rx.functions.Func1;

public class MessageRealmRepository implements Repository<Message> {

    private final RealmProvider mRealmProvider;
    private final Mapper<Message, MessageRealm> mToRealmMapper;
    private final Mapper<MessageRealm, Message> mToMessageMapper;

    public MessageRealmRepository(RealmProvider realmProvider,
                                  Mapper<Message, MessageRealm> toRealmMapper,
                                  Mapper<MessageRealm, Message> toMessageMapper) {
        mRealmProvider = realmProvider;
        mToRealmMapper = toRealmMapper;
        mToMessageMapper = toMessageMapper;
    }

    @Override
    public Completable add(Iterable<Message> items) {
        final RealmWrapper realmWrapper = new RealmWrapper();
        return Observable.from(items)
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .map(mToRealmMapper::from)
                .doOnNext(messageRealm -> realmWrapper.realm.insert(messageRealm))
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                })
                .toCompletable();
    }

    @Override
    public Completable update(Message item, Specification specification) {
        RealmWrapper realmWrapper = new RealmWrapper();
        MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification;
        return Observable.just(item)
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .map(mToRealmMapper::from)
                .doOnNext(newMessage -> {
                    realmSpecification.setMessageId(newMessage.getId());
                    MessageRealm messageToUpdate =
                            realmSpecification.toRealmQuery(realmWrapper.realm).findFirst();
                    if (messageToUpdate != null) {
                        newMessage.setUniqueId(messageToUpdate.getUniqueId());
                    }
                    realmWrapper.realm.copyToRealmOrUpdate(newMessage);
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                })
                .toCompletable();
    }

    @Override
    public Completable update(Iterable<Message> items, Specification specification) {
        final RealmWrapper realmWrapper = new RealmWrapper();
        MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification;

        return Observable.from(items)
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .map(mToRealmMapper::from)
                .doOnNext(newMessage -> {
                    realmSpecification.setMessageId(newMessage.getId());
                    MessageRealm messageToUpdate =
                            realmSpecification.toRealmQuery(realmWrapper.realm).findFirst();
                    if (messageToUpdate != null) {
                        newMessage.setUniqueId(messageToUpdate.getUniqueId());
                        newMessage.setFileDownloaded(messageToUpdate.isFileDownloaded());
                    }
                    realmWrapper.realm.copyToRealmOrUpdate(newMessage);
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                })
                .toCompletable();
    }

    public Observable<List<Message>> updateMessages(Iterable<Message> items, Specification specification) {
        final RealmWrapper realmWrapper = new RealmWrapper();
        MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification;

        return Observable.from(items)
                .flatMapIterable((Func1<Message, Iterable<Message>>) message -> items)
                .map(mToRealmMapper::from)
                .doOnNext(newMessage -> {
                    realmSpecification.setMessageId(newMessage.getId());
                    MessageRealm messageToUpdate =
                            realmSpecification.toRealmQuery(realmWrapper.realm).findFirst();
                    if (messageToUpdate != null) {
                        newMessage.setUniqueId(messageToUpdate.getUniqueId());
                        newMessage.setFileDownloaded(messageToUpdate.isFileDownloaded());
                    }
                    realmWrapper.realm.copyToRealmOrUpdate(newMessage);
                })
                .flatMap((Func1<MessageRealm, Observable<Message>>) messageRealm -> Observable.just(mToMessageMapper.from(messageRealm)))
                .toList()
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                });
    }

    @Override
    public Completable remove(Specification specification) {
        RealmWrapper realmWrapper = new RealmWrapper();
        MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification;

        return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm).findFirst())
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .doOnNext(messageRealm -> messageRealm.deleteFromRealm())
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                })
                .toCompletable();
    }

    @Override
    public Completable removeAll() {
        RealmWrapper realmWrapper = new RealmWrapper();

        return Observable.fromCallable((Callable<Void>) () -> { realmWrapper.realm.delete(MessageRealm.class); return null; })
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                })
                .toCompletable();
    }

    @Override
    public Observable<Message> queryForOneById(String id, Specification specification) {
        RealmWrapper realmWrapper = new RealmWrapper();
        MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification;
        ((MessageByIdSpecification) specification).setMessageId(id);

        return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm).findFirst())
                .map(mToMessageMapper::from)
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                });
    }

    @Override
    public Observable<List<Message>> queryForAll(Specification specification) {
        RealmWrapper realmWrapper = new RealmWrapper();
        RealmSpecification<MessageRealm> realmSpecification = (RealmSpecification) specification;

        return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm)
                .findAll()
                .sort(MessageRealm.CREATED_AT, Sort.DESCENDING))
                .flatMapIterable(talksRealms -> talksRealms)
                .flatMap((Func1<MessageRealm, Observable<Message>>) userRealm -> Observable.just(mToMessageMapper.from(userRealm)))
                .toList()
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                });
    }
}
