package ch.threema.app.routines;

import org.slf4j.Logger;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import ch.threema.app.services.UserService;
import static ch.threema.base.utils.LoggingKt.getThreemaLogger;
import ch.threema.data.models.ModelDeletedException;
import ch.threema.data.repositories.ContactModelRepository;
import ch.threema.domain.protocol.api.APIConnector;
import ch.threema.data.models.ContactModel;

public class UpdateFeatureLevelRoutine implements Runnable {
    private static final Logger logger = getThreemaLogger("UpdateFeatureLevelRoutine");

    private static final Map<String, Long> checkedIdentities = new HashMap<>();

    public static void removeTimeCache(@Nullable String identity) {
        synchronized (checkedIdentities) {
            checkedIdentities.remove(identity);
        }
    }

    @NonNull
    private final UserService userService;
    @NonNull
    private final APIConnector apiConnector;
    @NonNull
    private List<ContactModel> contactModels;

    public UpdateFeatureLevelRoutine(
        @NonNull ContactModelRepository contactModelRepository,
        @NonNull UserService userService,
        @NonNull APIConnector apiConnector,
        @Nullable List<String> identities
    ) {
        this.userService = userService;
        this.apiConnector = apiConnector;
        if (identities != null) {
            this.contactModels = identities
                .stream()
                .map(contactModelRepository::getByIdentity)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        } else {
            contactModels = Collections.emptyList();
        }
    }

    @Override
    @WorkerThread
    public void run() {
        logger.info("Running...");

        try {
            //remove "me" from list
            this.contactModels = this.contactModels.stream()
                .filter(c -> !userService.getIdentity().equals(c.getIdentity()))
                .collect(Collectors.toList());

            final Instant now = Instant.now();
            //leave the identity 1 hour in the cache!
            final long validTimestamp = now.minus(1, ChronoUnit.HOURS).toEpochMilli();

            synchronized (checkedIdentities) {
                // Leave only identities that have not been checked in the last hour
                List<ContactModel> filteredList = this.contactModels.stream()
                    .filter(contactModel -> {
                            Long checkedAt = checkedIdentities.get(contactModel.getIdentity());
                            return checkedAt == null || checkedAt < validTimestamp;
                        }
                    ).collect(Collectors.toList());

                logger.info("Running for {} entries", filteredList.size());

                if (!filteredList.isEmpty()) {
                    String[] identities = new String[filteredList.size()];

                    for (int n = 0; n < filteredList.size(); n++) {
                        identities[n] = filteredList.get(n).getIdentity();
                    }

                    try {
                        Long[] featureMasks = this.apiConnector.checkFeatureMask(identities);
                        for (int n = 0; n < featureMasks.length; n++) {
                            final Long featureMask = featureMasks[n];
                            if (featureMask == null) {
                                // Skip NULL values
                                logger.warn("Feature mask is null!");
                                continue;
                            }

                            ContactModel model = filteredList.get(n);
                            model.setFeatureMaskFromLocal(featureMask);
                            // Update checked identities cache
                            checkedIdentities.put(model.getIdentity(), now.toEpochMilli());
                        }
                    } catch (ModelDeletedException e) {
                        logger.warn("Model has been deleted", e);
                    } catch (Exception x) {
                        // Connection error
                        logger.error("Error while setting feature mask", x);
                    }
                }
            }

        } catch (Exception e) {
            logger.error("Error in run()", e);
        }

        logger.info("Done");
    }
}
