package de.beyondsoft.ownchat.data.repository;

import de.beyondsoft.ownchat.data.repository.mapper.Mapper;
import de.beyondsoft.ownchat.data.repository.models.GroupRealm;
import de.beyondsoft.ownchat.data.repository.models.UserRealm;
import de.beyondsoft.ownchat.data.repository.specifications.GroupByIdSpecification;
import de.beyondsoft.ownchat.data.repository.specifications.RealmSpecification;
import de.beyondsoft.ownchat.data.repository.specifications.Specification;
import de.beyondsoft.ownchat.model.Group;
import de.beyondsoft.ownchat.model.Message;
import de.beyondsoft.ownchat.model.User;

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

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

/**
 * Created by imarneanu on 3/6/17.
 */
public class GroupRealmRepository implements Repository<Group> {

    private final RealmProvider mRealmProvider;
    private final Mapper<Group, GroupRealm> mToRealmMapper;
    private final Mapper<GroupRealm, Group> mToGroupMapper;

    public GroupRealmRepository(RealmProvider realmProvider,
                                Mapper<Group, GroupRealm> toRealmMapper,
                                Mapper<GroupRealm, Group> toMessageMapper) {
        mRealmProvider = realmProvider;
        mToRealmMapper = toRealmMapper;
        mToGroupMapper = toMessageMapper;
    }

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

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

    @Override
    public Completable update(Iterable<Group> items, Specification specification) {
        final RealmWrapper realmWrapper = new RealmWrapper();
        GroupByIdSpecification realmSpecification = (GroupByIdSpecification) specification;
        return Observable.from(items)
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .map(mToRealmMapper::from)
                .doOnNext(newGroup -> {
                    realmSpecification.setGroupId(newGroup.getId());
                    GroupRealm groupToUpdate =
                            realmSpecification.toRealmQuery(realmWrapper.realm).findFirst();
                    if (groupToUpdate != null) {
                        if (newGroup.getMessage() != null && groupToUpdate.getMessage() != null &&
                                newGroup.getMessage().getCreatedAt() < groupToUpdate.getMessage().getCreatedAt()) {
                            return;
                        }
                        newGroup.setUniqueId(groupToUpdate.getUniqueId());
                        if (groupToUpdate.getMessage() != null
                                && newGroup.getMessage() != null
                                && groupToUpdate.getMessage().getUniqueId().equals(newGroup.getMessage().getUniqueId())
                                && groupToUpdate.getMessage().isRead()) {
                            newGroup.getMessage().setRead(true);
                        }
                    }
                    realmWrapper.realm.copyToRealmOrUpdate(newGroup);
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                })
                .toCompletable();
    }

    @Override
    public Completable remove(Specification specification) {
        return null;
    }

    @Override
    public Completable removeAll() {
        RealmWrapper realmWrapper = new RealmWrapper();
        return Observable.fromCallable(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                realmWrapper.realm.delete(GroupRealm.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<Group> queryForOneById(String id, Specification specification) {
        RealmWrapper realmWrapper = new RealmWrapper();
        GroupByIdSpecification realmSpecification = (GroupByIdSpecification) specification;
        ((GroupByIdSpecification) specification).setGroupId(Integer.parseInt(id));
        return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm).findFirst())
                .map(mToGroupMapper::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<Group>> queryForAll(Specification specification) {
        RealmWrapper realmWrapper = new RealmWrapper();
        RealmSpecification<GroupRealm> realmSpecification = (RealmSpecification) specification;
        return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm)
                .findAllSorted(GroupRealm.CREATED_AT, Sort.DESCENDING))
                .flatMapIterable(groupsRealms -> groupsRealms)
                .flatMap(new Func1<GroupRealm, Observable<Group>>() {
                    @Override
                    public Observable<Group> call(GroupRealm groupRealm) {
                        return Observable.just(mToGroupMapper.from(groupRealm));
                    }
                })
                .toList()
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                });
    }

    public Completable updateGroupMessage(Group group, Specification specification) {
        final RealmWrapper realmWrapper = new RealmWrapper();
        GroupByIdSpecification realmSpecification = (GroupByIdSpecification) specification;
        return Observable.just(group)
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .map(mToRealmMapper::from)
                .doOnNext(newGroup -> {
                    realmSpecification.setGroupId(newGroup.getId());
                    GroupRealm groupToUpdate =
                            realmSpecification.toRealmQuery(realmWrapper.realm).findFirst();
                    if (groupToUpdate == null) {
                        return;
                    }
                    if (groupToUpdate.getMessage() != null &&
                            group.lastMessage.createdAt < (groupToUpdate.getMessage().getCreatedAt() / 1000)) {
                        return;
                    }
                    RealmList<UserRealm> userRealms = groupToUpdate.getUserIdList();
                    ArrayList<User> users = null;
                    if (userRealms != null) {
                        users = new ArrayList<>(userRealms.size());
                        for (int i = 0, size = userRealms.size(); i < size; i++) {
                            users.add(User.createFromUserRealm(userRealms.get(i)));
                        }
                    }
                    Message message = group.lastMessage;
                    if (groupToUpdate.getMessage() != null
                            && groupToUpdate.getMessage().getUniqueId().equals(message.getUniqueId())
                            && groupToUpdate.getMessage().isRead()) {
                        message.isRead = true;
                    }
                    newGroup = new GroupRealm(groupToUpdate.getId(),
                            groupToUpdate.getName(),
                            groupToUpdate.getAvatar(),
                            group.lastMessage,
                            groupToUpdate.getMobile(),
                            groupToUpdate.isOnlyEmployees(),
                            users,
                            groupToUpdate.getUserId());
                    realmWrapper.realm.copyToRealmOrUpdate(newGroup);
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                })
                .toCompletable();
    }
}
