package com.p3.bewoplaner.data.repository;

import com.p3.bewoplaner.data.repository.mapper.Mapper;
import com.p3.bewoplaner.data.repository.models.UserRealm;
import com.p3.bewoplaner.data.repository.specifications.RealmSpecification;
import com.p3.bewoplaner.data.repository.specifications.Specification;
import com.p3.bewoplaner.data.repository.specifications.UserByIdSpecification;
import com.p3.bewoplaner.model.User;

import android.util.Log;

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

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

/**
 * Created by imarneanu on 3/6/17.
 */
public class UserRealmRepository implements Repository<User> {
    private final RealmProvider mRealmProvider;
    private final Mapper<User, UserRealm> mToRealmMapper;
    private final Mapper<UserRealm, User> mToUserMapper;

    public UserRealmRepository(RealmProvider realmProvider,
                               Mapper<User, UserRealm> toRealmMapper,
                               Mapper<UserRealm, User> toUserMapper) {
        mRealmProvider = realmProvider;
        mToRealmMapper = toRealmMapper;
        mToUserMapper = toUserMapper;
    }

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

    @Override
    public Completable update(User item, Specification specification) {
        RealmWrapper realmWrapper = new RealmWrapper();
        UserByIdSpecification realmSpecification = (UserByIdSpecification) specification;
        return Observable.just(item)
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .map(mToRealmMapper::from)
                .doOnNext(newUser -> {
                    realmSpecification.setUserId(newUser.getId());
                    UserRealm userToUpdate =
                            realmSpecification.toRealmQuery(realmWrapper.realm).findFirst();
                    if (userToUpdate != null) {
                        newUser.setId(userToUpdate.getId());
                    }
                    realmWrapper.realm.copyToRealmOrUpdate(newUser);
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                })
                .toCompletable();
    }

    @Override
    public Completable update(Iterable<User> items, Specification specification) {
        final RealmWrapper realmWrapper = new RealmWrapper();
        UserByIdSpecification realmSpecification = (UserByIdSpecification) specification;
        return Observable.from(items)
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .map(mToRealmMapper::from)
                .doOnNext(newUser -> {
                    realmSpecification.setUserId(newUser.getId());
                    UserRealm userToUpdate =
                            realmSpecification.toRealmQuery(realmWrapper.realm).findFirst();
                    if (userToUpdate != null) {
                        newUser.setId(userToUpdate.getId());
                    }
                    realmWrapper.realm.copyToRealmOrUpdate(newUser);
                })
                .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 Observable<User> queryForOneById(String id, Specification specification) {
        RealmWrapper realmWrapper = new RealmWrapper();
        UserByIdSpecification realmSpecification = (UserByIdSpecification) specification;
        Log.e("USER", "long value: " + Long.parseLong(id));
        ((UserByIdSpecification) specification).setUserId(Long.parseLong(id));
        return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm).findFirst())
                .map(mToUserMapper::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<User>> queryForAll(Specification specification) {
        RealmWrapper realmWrapper = new RealmWrapper();
        RealmSpecification<UserRealm> realmSpecification = (RealmSpecification) specification;
        return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm)
                .findAllSorted(UserRealm.FIRST_NAME, Sort.ASCENDING))
                .flatMapIterable(userRealms -> userRealms)
                .flatMap(new Func1<UserRealm, Observable<User>>() {
                    @Override
                    public Observable<User> call(UserRealm userRealm) {
                        return Observable.just(mToUserMapper.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();
                });
    }

    @Override
    public Completable removeAll() {
        RealmWrapper realmWrapper = new RealmWrapper();
        return Observable.fromCallable(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                realmWrapper.realm.delete(UserRealm.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();
    }
}
