package de.beyondsoft.ownchat.data.repository;

import android.util.Log;

import de.beyondsoft.ownchat.BewoplanerApplication;
import de.beyondsoft.ownchat.data.repository.mapper.Mapper;
import de.beyondsoft.ownchat.data.repository.models.GroupRealm;
import de.beyondsoft.ownchat.data.repository.models.MessageRealm;
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.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.Callable;

import io.realm.RealmList;
import io.realm.RealmResults;
import io.realm.Sort;
import rx.Completable;
import rx.Observable;

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;

        ArrayList<Group> newGroups = (ArrayList<Group>) items;
        ArrayList<Long> newUserIds = new ArrayList<>();
        ArrayList<Long> oldUserIds = new ArrayList<>();
        ArrayList<Long> userIdsToDelete = new ArrayList<>();

        Long myself = ((GroupByIdSpecification) specification).getUserId();

        if(newGroups.size() > 0) {
            newUserIds = getUserIdsFromGroups(newGroups, myself);
        }

        int itemsSize = newGroups.size();

        Long[] ids = new Long[itemsSize];
        for(int i = 0; i < ids.length; i++) {
            ids[i] = newGroups.get(i).id;
        }

        List<String> filePaths = new ArrayList<>();
        boolean hasGroupsToDelete = false;
        boolean hasMessagesToDelete = false;

        try {
            realmWrapper.realm = mRealmProvider.provide();
            realmWrapper.realm.beginTransaction();

            RealmResults<GroupRealm> allWhoAreNotComingFromTheServer = realmWrapper.realm.where(GroupRealm.class).not().in(GroupRealm.ID, ids).findAll();
            RealmResults<MessageRealm> messagesToDelete = realmWrapper.realm.where(MessageRealm.class).not().in(MessageRealm.GROUP_ID, ids).findAll();
            RealmResults<GroupRealm> allOldGroups = realmWrapper.realm.where(GroupRealm.class).findAll();

            for(GroupRealm groupRealm : allOldGroups) {
                for(UserRealm userRealm : groupRealm.getUserIdList()) {
                    oldUserIds.add(userRealm.getId());
                }
            }

            for(Long id : oldUserIds) {
                if(!newUserIds.contains(id) && !Objects.equals(id, myself) && !userIdsToDelete.contains(id)) {
                    userIdsToDelete.add(id);
                }
            }

            for(MessageRealm mr : messagesToDelete) {
                if(mr.isFileDownloaded()) {
                    filePaths.add(mr.getFilePath());
                }
            }

            hasGroupsToDelete = allWhoAreNotComingFromTheServer.size() > 0;
            hasMessagesToDelete = messagesToDelete.size() > 0;

            realmWrapper.realm.commitTransaction();
            realmWrapper.realm.close();
        } catch(Exception e) {
            RealmTransactionLog(e.getMessage(), 2);

            realmWrapper.realm.cancelTransaction();
            realmWrapper.realm.close();
        }

        if(hasGroupsToDelete) {
            removeGroupsByIds(ids);
        }

        if(userIdsToDelete.size() > 0) {
            removeUsersByIds(userIdsToDelete.toArray(new Long[userIdsToDelete.size()]));
        }

        if(hasMessagesToDelete) {
            removeMessagesByGroupIds(ids);

            for (String path : filePaths) {
                String[] splitFileName = path.split("/");
                String fileName = splitFileName[splitFileName.length - 1];
                File fileToDelete = new File(BewoplanerApplication.getAppContext().getExternalFilesDir(null), File.separator + fileName);

                boolean fileExists = fileToDelete.exists();

                if(fileExists) {
                    fileToDelete.delete();
                }
            }
        }

        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 -> {
                    RealmTransactionLog(String.format(Locale.getDefault(), "Error while updating groups: %s", throwable.getMessage()), 2);
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                })
                .toCompletable();
    }

    private ArrayList<Long> getUserIdsFromGroups(ArrayList<Group> groups, long myself) {
        ArrayList<Long> userIds = new ArrayList<>();

        for(Group group : groups) {
            for(User user : group.users) {
                if(user.id == myself) {
                    continue;
                }

                userIds.add(user.id);
            }
        }


        return userIds;
    }

    private void RealmTransactionLog(String message, int mode) {
        final String tag = "REALM_TRANSACTION";

        switch(mode) {
            case 0:
                Log.d(tag, message);
                break;
            case 1:
                Log.i(tag, message);
                break;
            case 2:
                Log.e(tag, message);
                break;
            default:
                Log.wtf(tag, message);
                break;
        }
    }

    private void removeGroupsByIds(Long[] groupIds) {
        final RealmWrapper realmWrapper = new RealmWrapper();

        try {
            realmWrapper.realm = mRealmProvider.provide();
            realmWrapper.realm.beginTransaction();

            realmWrapper.realm.where(GroupRealm.class).not().in(GroupRealm.ID, groupIds).findAll().deleteAllFromRealm();

            realmWrapper.realm.commitTransaction();
            realmWrapper.realm.close();
        } catch(Exception e) {
            Log.e("REALM_TRANSACTION", "Error while removing groups: " + e.getMessage());

            realmWrapper.realm.cancelTransaction();
            realmWrapper.realm.close();
        }
    }

    private void removeMessagesByGroupIds(Long[] groupIds) {
        final RealmWrapper realmWrapper = new RealmWrapper();

        try {
            realmWrapper.realm = mRealmProvider.provide();
            realmWrapper.realm.beginTransaction();

            realmWrapper.realm.where(MessageRealm.class).not().in(MessageRealm.GROUP_ID, groupIds).findAll().deleteAllFromRealm();

            realmWrapper.realm.commitTransaction();
            realmWrapper.realm.close();
        } catch(Exception e) {
            Log.e("REALM_TRANSACTION", "Error while removing messages from removed groups: " + e.getMessage());

            realmWrapper.realm.cancelTransaction();
            realmWrapper.realm.close();
        }
    }

    private void removeUsersByIds(Long[] userIds) {
        final RealmWrapper realmWrapper = new RealmWrapper();

        try {
            realmWrapper.realm = mRealmProvider.provide();
            realmWrapper.realm.beginTransaction();

            realmWrapper.realm.where(UserRealm.class).in(UserRealm.ID, userIds).findAll().deleteAllFromRealm();

            realmWrapper.realm.commitTransaction();
            realmWrapper.realm.close();
        } catch(Exception e) {
            Log.e("REALM_TRANSACTION", "Error while removing users: " + e.getMessage());

            realmWrapper.realm.cancelTransaction();
            realmWrapper.realm.close();
        }
    }

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

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

        return Observable.fromCallable((Callable<Void>) () -> {
            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)
                .findAll()
                .sort(GroupRealm.CREATED_AT, Sort.DESCENDING))
                .flatMapIterable(groupsRealms -> groupsRealms)
                .flatMap(groupRealm -> 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();
    }
}
