package org.dhis2.usescases.settings;

import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.ExistingWorkPolicy;

import org.dhis2.data.prefs.PreferenceProvider;
import org.dhis2.data.schedulers.SchedulerProvider;
import org.dhis2.data.service.workManager.WorkManagerController;
import org.dhis2.data.service.workManager.WorkerItem;
import org.dhis2.data.service.workManager.WorkerType;
import org.dhis2.usescases.login.LoginActivity;
import org.dhis2.usescases.reservedValue.ReservedValueActivity;
import org.dhis2.usescases.settings.models.SettingsViewModel;
import org.dhis2.utils.Constants;
import org.dhis2.utils.analytics.AnalyticsHelper;
import org.hisp.dhis.android.core.D2;
import org.hisp.dhis.android.core.maintenance.D2Error;
import org.hisp.dhis.android.core.settings.LimitScope;

import java.io.File;

import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.processors.FlowableProcessor;
import io.reactivex.processors.PublishProcessor;
import timber.log.Timber;

import static org.dhis2.utils.analytics.AnalyticsConstants.CLICK;
import static org.dhis2.utils.analytics.AnalyticsConstants.SYNC_DATA_NOW;
import static org.dhis2.utils.analytics.AnalyticsConstants.SYNC_METADATA_NOW;


public class SyncManagerPresenter implements SyncManagerContracts.Presenter {

    private final D2 d2;
    private final SchedulerProvider schedulerProvider;
    private final PreferenceProvider preferenceProvider;
    private final SettingsRepository settingsRepository;
    private final AnalyticsHelper analyticsHelper;
    private CompositeDisposable compositeDisposable;
    private SyncManagerContracts.View view;
    private FlowableProcessor<Boolean> checkData;
    private GatewayValidator gatewayValidator;
    private WorkManagerController workManagerController;

    SyncManagerPresenter(
            D2 d2,
            SchedulerProvider schedulerProvider,
            GatewayValidator gatewayValidator,
            PreferenceProvider preferenceProvider,
            WorkManagerController workManagerController,
            SettingsRepository settingsRepository,
            SyncManagerContracts.View view,
            AnalyticsHelper analyticsHelper) {
        this.view = view;
        this.d2 = d2;
        this.settingsRepository = settingsRepository;
        this.schedulerProvider = schedulerProvider;
        this.preferenceProvider = preferenceProvider;
        this.gatewayValidator = gatewayValidator;
        this.workManagerController = workManagerController;
        this.analyticsHelper = analyticsHelper;
        checkData = PublishProcessor.create();
        compositeDisposable = new CompositeDisposable();
    }

    @Override
    public void onItemClick(SettingItem settingsItem) {
        view.openItem(settingsItem);
    }

    @Override
    public void init() {
        compositeDisposable.add(
                checkData.startWith(true)
                        .flatMapSingle(start ->
                                Single.zip(
                                        settingsRepository.metaSync(),
                                        settingsRepository.dataSync(),
                                        settingsRepository.syncParameters(),
                                        settingsRepository.reservedValues(),
                                        settingsRepository.sms(),
                                        SettingsViewModel::new
                                ))
                        .subscribeOn(schedulerProvider.io())
                        .observeOn(schedulerProvider.ui())
                        .subscribe(
                                settingsViewModel -> {
                                    view.setMetadataSettings(settingsViewModel.getMetadataSettingsViewModel());
                                    view.setDataSettings(settingsViewModel.getDataSettingsViewModel());
                                    view.setParameterSettings(settingsViewModel.getSyncParametersViewModel());
                                    view.setReservedValuesSettings(settingsViewModel.getReservedValueSettingsViewModel());
                                    view.setSMSSettings(settingsViewModel.getSmsSettingsViewModel());
                                },
                                Timber::e
                        ));
    }

    @Override
    public int getMetadataPeriodSetting() {
        return settingsRepository.metaSync()
                .blockingGet()
                .getMetadataSyncPeriod();
    }

    @Override
    public int getDataPeriodSetting() {
        return settingsRepository.dataSync()
                .blockingGet()
                .getDataSyncPeriod();
    }

    public void validateGatewayObservable(String gateway) {
        if (plusIsMissingOrIsTooLong(gateway)) {
            view.showInvalidGatewayError();
        } else if (gateway.isEmpty()) {
            view.requestNoEmptySMSGateway();
        } else if (isValidGateway(gateway)) {
            view.hideGatewayError();
        }
    }

    private boolean isValidGateway(String gateway) {
        return gatewayValidator.validate(gateway) ||
                (gateway.startsWith("+") && gateway.length() == 1);
    }

    private boolean plusIsMissingOrIsTooLong(String gateway) {
        return (!gateway.startsWith("+") && gateway.length() == 1) ||
                (gateway.length() >= GatewayValidator.Companion.getMax_size());
    }

    public boolean isGatewaySetAndValid(String gateway) {
        if (gateway.isEmpty()) {
            view.requestNoEmptySMSGateway();
            return false;
        } else if (!gatewayValidator.validate(gateway)) {
            view.showInvalidGatewayError();
            return false;
        }
        return true;
    }

    @Override
    public void saveLimitScope(LimitScope limitScope) {
        settingsRepository.saveLimitScope(limitScope);
        checkData.onNext(true);
    }

    @Override
    public void saveEventMaxCount(Integer eventsNumber) {
        settingsRepository.saveEventsToDownload(eventsNumber);
        checkData.onNext(true);
    }

    @Override
    public void saveTeiMaxCount(Integer teiNumber) {
        settingsRepository.saveTeiToDownload(teiNumber);
        checkData.onNext(true);
    }

    @Override
    public void saveReservedValues(Integer reservedValuesCount) {
        settingsRepository.saveReservedValuesToDownload(reservedValuesCount);
        checkData.onNext(true);
    }

    @Override
    public void saveGatewayNumber(String gatewayNumber) {
        if (isGatewaySetAndValid(gatewayNumber)) {
            settingsRepository.saveGatewayNumber(gatewayNumber);
        }
    }

    @Override
    public void saveSmsResultSender(String smsResultSender) {
        settingsRepository.saveSmsResultSender(smsResultSender);
    }

    @Override
    public void saveSmsResponseTimeout(Integer smsResponseTimeout) {
        settingsRepository.saveSmsResponseTimeout(smsResponseTimeout);
    }

    @Override
    public void saveWaitForSmsResponse(boolean shouldWait) {
        settingsRepository.saveWaitForSmsResponse(shouldWait);
    }

    @Override
    public void enableSmsModule(boolean enableSms) {
        if (enableSms) {
            view.displaySMSRefreshingData();
        }
        compositeDisposable.add(
                settingsRepository.enableSmsModule(enableSms)
                        .subscribeOn(schedulerProvider.io())
                        .observeOn(schedulerProvider.ui())
                        .subscribe(
                                () -> view.displaySMSEnabled(enableSms),
                                error -> {
                                    Timber.e(error);
                                    view.displaySmsEnableError();
                                }
                        )
        );
    }

    @Override
    public void syncData(int seconds, String scheduleTag) {
        preferenceProvider.setValue(Constants.TIME_DATA, seconds);
        workManagerController.cancelUniqueWork(scheduleTag);
        WorkerItem workerItem = new WorkerItem(scheduleTag, WorkerType.DATA, (long) seconds, null, null, ExistingPeriodicWorkPolicy.REPLACE);
        workManagerController.enqueuePeriodicWork(workerItem);
        checkData();
    }

    @Override
    public void syncMeta(int seconds, String scheduleTag) {
        preferenceProvider.setValue(Constants.TIME_META, seconds);
        workManagerController.cancelUniqueWork(scheduleTag);
        WorkerItem workerItem = new WorkerItem(scheduleTag, WorkerType.METADATA, (long) seconds, null, null, ExistingPeriodicWorkPolicy.REPLACE);
        workManagerController.enqueuePeriodicWork(workerItem);
        checkData();
    }

    @Override
    public void syncData() {
        view.syncData();
        analyticsHelper.setEvent(SYNC_DATA_NOW, CLICK, SYNC_DATA_NOW);
        WorkerItem workerItem = new WorkerItem(Constants.DATA_NOW, WorkerType.DATA, null, null, ExistingWorkPolicy.KEEP, null);
        workManagerController.syncDataForWorker(workerItem);
        checkData();
    }

    @Override
    public void syncMeta() {
        view.syncMeta();
        analyticsHelper.setEvent(SYNC_METADATA_NOW, CLICK, SYNC_METADATA_NOW);
        WorkerItem workerItem = new WorkerItem(Constants.META_NOW, WorkerType.METADATA, null, null, ExistingWorkPolicy.KEEP, null);
        workManagerController.syncDataForWorker(workerItem);
    }

    @Override
    public void cancelPendingWork(String tag) {
        preferenceProvider.setValue(tag.equals(Constants.DATA) ? Constants.TIME_DATA : Constants.TIME_META, 0);
        workManagerController.cancelUniqueWork(tag);
        checkData();
    }

    @Override
    public void dispose() {
        compositeDisposable.clear();
    }

    @Override
    public void resetSyncParameters() {
        preferenceProvider.setValue(Constants.EVENT_MAX, Constants.EVENT_MAX_DEFAULT);
        preferenceProvider.setValue(Constants.TEI_MAX, Constants.TEI_MAX_DEFAULT);
        preferenceProvider.setValue(Constants.LIMIT_BY_ORG_UNIT, false);
        preferenceProvider.setValue(Constants.LIMIT_BY_PROGRAM, false);

        checkData.onNext(true);
    }

    @Override
    public void onWipeData() {
        view.wipeDatabase();
    }

    @Override
    public void wipeDb() {
        try {
            workManagerController.cancelAllWork();
            workManagerController.pruneWork();
            // clearing cache data
            deleteDir(view.getAbstracContext().getCacheDir());

            preferenceProvider.clear();

            d2.wipeModule().wipeEverything();
            d2.userModule().logOut().blockingAwait();

        } catch (Exception e) {
            Timber.e(e);
        } finally {
            view.startActivity(LoginActivity.class, null, true, true, null);
        }
    }

    @Override
    public void onDeleteLocalData() {
        view.deleteLocalData();
    }

    @Override
    public void deleteLocalData() {
        boolean error = false;
        try {
            d2.wipeModule().wipeData();
        } catch (D2Error e) {
            Timber.e(e);
            error = true;
        }

        view.showLocalDataDeleted(error);
    }

    @Override
    public void onReservedValues() {
        view.startActivity(ReservedValueActivity.class, null, false, false, null);
    }

    @Override
    public void checkSyncErrors() {
        view.showSyncErrors(d2.maintenanceModule().d2Errors().blockingGet());
    }

    @Override
    public void checkData() {
        checkData.onNext(true);
    }

    private static boolean deleteDir(File dir) {
        if (dir != null && dir.isDirectory()) {
            String[] children = dir.list();
            for (String aChildren : children) {
                boolean success = deleteDir(new File(dir, aChildren));
                if (!success) {
                    return false;
                }
            }
            return dir.delete();
        } else if (dir != null && dir.isFile()) {
            return dir.delete();
        } else {
            return false;
        }
    }
}