'use strict';

/* global $, angular, TASK_TYPES, SECRET_PLACEHOLDER */

angular.module('Application').controller('BackupsController', ['$scope', '$location', '$rootScope', '$timeout', 'Client', function ($scope, $location, $rootScope, $timeout, Client) {
    Client.onReady(function () { if (!Client.getUserInfo().isAtLeastAdmin) $location.path('/'); });

    $scope.SECRET_PLACEHOLDER = SECRET_PLACEHOLDER;

    $scope.config = Client.getConfig();
    $scope.user = Client.getUserInfo();
    $scope.memory = null; // { memory, swap }

    $scope.manualBackupApps = [];

    $scope.backupCheck = { ok: true, message: '' };
    $scope.backupConfig = {};
    $scope.backups = [];

    // List is from http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
    $scope.s3Regions = [
        { name: 'Asia Pacific (Mumbai)', value: 'ap-south-1' },
        { name: 'Asia Pacific (Osaka-Local)', value: 'ap-northeast-3' },
        { name: 'Asia Pacific (Seoul)', value: 'ap-northeast-2' },
        { name: 'Asia Pacific (Singapore)', value: 'ap-southeast-1' },
        { name: 'Asia Pacific (Sydney)', value: 'ap-southeast-2' },
        { name: 'Asia Pacific (Tokyo)', value: 'ap-northeast-1' },
        { name: 'Canada (Central)', value: 'ca-central-1' },
        { name: 'China (Beijing)', value: 'cn-north-1' },
        { name: 'China (Ningxia)', value: 'cn-northwest-1' },
        { name: 'EU (Frankfurt)', value: 'eu-central-1' },
        { name: 'EU (Ireland)', value: 'eu-west-1' },
        { name: 'EU (London)', value: 'eu-west-2' },
        { name: 'EU (Paris)', value: 'eu-west-3' },
        { name: 'EU (Stockholm)', value: 'eu-north-1' },
        { name: 'South America (São Paulo)', value: 'sa-east-1' },
        { name: 'US East (N. Virginia)', value: 'us-east-1' },
        { name: 'US East (Ohio)', value: 'us-east-2' },
        { name: 'US West (N. California)', value: 'us-west-1' },
        { name: 'US West (Oregon)', value: 'us-west-2' },
    ];

    $scope.wasabiRegions = [
        { name: 'EU Central 1', value: 'https://s3.eu-central-1.wasabisys.com' },
        { name: 'US East 1', value: 'https://s3.us-east-1.wasabisys.com' },
        { name: 'US East 2', value: 'https://s3.us-east-2.wasabisys.com ' },
        { name: 'US West 1', value: 'https://s3.us-west-1.wasabisys.com' }
    ];

    $scope.doSpacesRegions = [
        { name: 'AMS3', value: 'https://ams3.digitaloceanspaces.com' },
        { name: 'FRA1', value: 'https://fra1.digitaloceanspaces.com' },
        { name: 'NYC3', value: 'https://nyc3.digitaloceanspaces.com' },
        { name: 'SFO2', value: 'https://sfo2.digitaloceanspaces.com' },
        { name: 'SFO3', value: 'https://sfo3.digitaloceanspaces.com' },
        { name: 'SGP1', value: 'https://sgp1.digitaloceanspaces.com' }
    ];

    $scope.exoscaleSosRegions = [
        { name: 'AT-VIE-1', value: 'https://sos-at-vie-1.exo.io' },
        { name: 'CH-DK-2', value: 'https://sos-ch-dk-2.exo.io' },
        { name: 'CH-GVA-2', value: 'https://sos-ch-gva-2.exo.io' },
        { name: 'DE-FRA-1', value: 'https://sos-de-fra-1.exo.io' },
    ];

    // https://www.scaleway.com/docs/object-storage-feature/
    $scope.scalewayRegions = [
        { name: 'FR-PAR', value: 'https://s3.fr-par.scw.cloud', region: 'fr-par' }, // default
        { name: 'NL-AMS', value: 'https://s3.nl-ams.scw.cloud', region: 'nl-ams' }
    ];

    $scope.linodeRegions = [
        { name: 'Newark', value: 'us-east-1.linodeobjects.com', region: 'us-east-1' }, // default
        { name: 'Frankfurt', value: 'eu-central-1.linodeobjects.com', region: 'us-east-1' },
        { name: 'Singapore', value: 'ap-south-1.linodeobjects.com', region: 'us-east-1' }
    ];

    // note: ovh also has a storage endpoint but that only supports path style access
    $scope.ovhRegions = [
        { name: 'Beauharnois (BHS)', value: 'https://s3.bhs.cloud.ovh.net', region: 'bhs' }, // default
        { name: 'Frankfurt (DE)', value: 'https://s3.de.cloud.ovh.net', region: 'de' },
        { name: 'Gravelines (GRA)', value: 'https://s3.gra.cloud.ovh.net', region: 'gra' },
        { name: 'Strasbourg (SBG)', value: 'https://s3.sbg.cloud.ovh.net', region: 'sbg' },
        { name: 'London (UK)', value: 'https://s3.uk.cloud.ovh.net', region: 'uk' },
        { name: 'Sydney (SYD)', value: 'https://s3.syd.cloud.ovh.net', region: 'syd' },
        { name: 'Warsaw (WAW)', value: 'https://s3.waw.cloud.ovh.net', region: 'waw' },
    ];

    // https://devops.ionos.com/api/s3/
    $scope.ionosRegions = [
        { name: 'DE', value: 'https://s3-de-central.profitbricks.com', region: 's3-de-central' }, // default
    ];

    $scope.storageProvider = [
        { name: 'Amazon S3', value: 's3' },
        { name: 'Backblaze B2 (S3 API)', value: 'backblaze-b2' },
        { name: 'CIFS Mount', value: 'cifs' },
        { name: 'DigitalOcean Spaces', value: 'digitalocean-spaces' },
        { name: 'Exoscale SOS', value: 'exoscale-sos' },
        { name: 'Filesystem', value: 'filesystem' },
        { name: 'Google Cloud Storage', value: 'gcs' },
        { name: 'IONOS (Profitbricks)', value: 'ionos-objectstorage' },
        { name: 'Linode Object Storage', value: 'linode-objectstorage' },
        { name: 'Minio', value: 'minio' },
        { name: 'NFS Mount', value: 'nfs' },
        { name: 'OVH Object Storage', value: 'ovh-objectstorage' },
        { name: 'S3 API Compatible (v4)', value: 's3-v4-compat' },
        { name: 'Scaleway Object Storage', value: 'scaleway-objectstorage' },
        { name: 'SSHFS Mount', value: 'sshfs' },
        { name: 'Wasabi', value: 'wasabi' },
        { name: 'No-op (Only for testing)', value: 'noop' }
    ];

    $scope.retentionPolicies = [
        { name: '2 days', value: { keepWithinSecs: 2 * 24 * 60 * 60 }},
        { name: '1 week', value: { keepWithinSecs: 7 * 24 * 60 * 60 }}, // default
        { name: '1 month', value: { keepWithinSecs: 30 * 24 * 60 * 60 }},
        { name: '2 daily, 4 weekly', value: { keepDaily: 2, keepWeekly: 4 }},
        { name: '3 daily, 4 weekly, 6 monthly', value: { keepDaily: 3, keepWeekly: 4, keepMonthly: 6 }},
        { name: '7 daily, 4 weekly, 12 monthly', value: { keepDaily: 7, keepWeekly: 4, keepMonthly: 12 }},
        { name: 'Forever', value: { keepWithinSecs: -1 }}
    ];

    // values correspond to cron days
    $scope.cronDays = [
        { name: 'Sunday', value: 0 },
        { name: 'Monday', value: 1 },
        { name: 'Tuesday', value: 2 },
        { name: 'Wednesday', value: 3 },
        { name: 'Thursday', value: 4 },
        { name: 'Friday', value: 5 },
        { name: 'Saturday', value: 6 },
    ];

    $scope.cronHours = [
        { name: 'Midnight', value: 0 }, { name: '1 AM', value: 1 }, { name: '2 AM', value: 2 }, { name: '3 AM', value: 3 },
        { name: '4 AM', value: 4 }, { name: '5 AM', value: 5 }, { name: '6 AM', value: 6 }, { name: '7 AM', value: 7 },
        { name: '8 AM', value: 8 }, { name: '9 AM', value: 9 }, { name: '10 AM', value: 10 }, { name: '11 AM', value: 11 },
        { name: 'Noon', value: 12 }, { name: '1 PM', value: 13 }, { name: '2 PM', value: 14 }, { name: '3 PM', value: 15 },
        { name: '4 PM', value: 16 }, { name: '5 PM', value: 17 }, { name: '6 PM', value: 18 }, { name: '7 PM', value: 19 },
        { name: '8 PM', value: 20 }, { name: '9 PM', value: 21 }, { name: '10 PM', value: 22 }, { name: '11 PM', value: 23 }
    ];

    $scope.formats = [
        { name: 'Tarball (zipped)', value: 'tgz' },
        { name: 'rsync', value: 'rsync' }
    ];

    $scope.prettyProviderName = function (provider) {
        switch (provider) {
        case 'caas': return 'Managed Cloudron';
        default: return provider;
        }
    };

    $scope.prettyBackupSchedule = function (pattern) {
        if (!pattern) return '';
        var tmp = pattern.split(' ');
        var hours = tmp[2].split(','), days = tmp[5].split(',');
        var prettyDay;
        if (days.length === 7 || days[0] === '*') {
            prettyDay = 'Everyday';
        } else  {
            prettyDay = days.map(function (day) { return $scope.cronDays[parseInt(day, 10)].name.substr(0, 3); }).join(',');
        }

        var prettyHour = hours.map(function (hour) { return $scope.cronHours[parseInt(hour, 10)].name; }).join(',');

        return prettyDay + ' at ' + prettyHour;
    };

    $scope.prettyBackupRetentionPolicy = function (retentionPolicy) {
        var tmp = $scope.retentionPolicies.find(function (p) { return angular.equals(p.value, retentionPolicy); });
        return tmp ? tmp.name : '';
    };

    $scope.createBackup = {
        busy: false,
        percent: 0,
        message: '',
        errorMessage: '',
        taskId: '',
        taskType: TASK_TYPES.TASK_BACKUP,

        checkStatus: function () {
            // TODO support both task types TASK_BACKUP and TASK_CLEAN_BACKUPS
            Client.getLatestTaskByType($scope.createBackup.taskType, function (error, task) {
                if (error) return console.error(error);

                if (!task) return;

                $scope.createBackup.taskId = task.id;
                $scope.createBackup.updateStatus();
            });
        },

        updateStatus: function () {
            Client.getTask($scope.createBackup.taskId, function (error, data) {
                if (error) return window.setTimeout($scope.createBackup.updateStatus, 5000);

                if (!data.active) {
                    $scope.createBackup.busy = false;
                    $scope.createBackup.message = '';
                    $scope.createBackup.percent = 100; // indicates that 'result' is valid
                    $scope.createBackup.errorMessage = data.success ? '' : data.error.message;

                    return fetchBackups();
                }

                $scope.createBackup.busy = true;
                $scope.createBackup.percent = data.percent;
                $scope.createBackup.message = data.message;
                window.setTimeout($scope.createBackup.updateStatus, 3000);
            });
        },

        startBackup: function () {
            $scope.createBackup.busy = true;
            $scope.createBackup.percent = 0;
            $scope.createBackup.message = '';
            $scope.createBackup.errorMessage = '';
            $scope.createBackup.taskType = TASK_TYPES.TASK_BACKUP;

            Client.startBackup(function (error, taskId) {
                if (error) {
                    if (error.statusCode === 409 && error.message.indexOf('full_backup') !== -1) {
                        $scope.createBackup.errorMessage = 'Backup already in progress. Please retry later.';
                    } else if (error.statusCode === 409) {
                        $scope.createBackup.errorMessage = 'App task is currently in progress. Please retry later.';
                    } else {
                        console.error(error);
                        $scope.createBackup.errorMessage = error.message;
                    }

                    $scope.createBackup.busy = false;
                    $('#createBackupFailedModal').modal('show');

                    return;
                }

                $scope.createBackup.taskId = taskId;
                $scope.createBackup.updateStatus();
            });
        },

        cleanupBackups: function () {
            $('#cleanupBackupsModal').modal('show');
        },

        startCleanup: function () {
            $scope.createBackup.busy = true;
            $scope.createBackup.percent = 0;
            $scope.createBackup.message = '';
            $scope.createBackup.errorMessage = '';
            $scope.createBackup.taskType = TASK_TYPES.TASK_CLEAN_BACKUPS;

            $('#cleanupBackupsModal').modal('hide');

            Client.cleanupBackups(function (error, taskId) {
                if (error) console.error(error);

                $scope.createBackup.taskId = taskId;
                $scope.createBackup.updateStatus();
            });
        },

        stopTask: function () {
            Client.stopTask($scope.createBackup.taskId, function (error) {
                if (error) {
                    if (error.statusCode === 409) {
                        $scope.createBackup.errorMessage = 'No backup is currently in progress';
                    } else {
                        console.error(error);
                        $scope.createBackup.errorMessage = error.message;
                    }

                    $scope.createBackup.busy = false;

                    return;
                }
            });
        }
    };

    $scope.listBackups = {
    };

    $scope.s3like = function (provider) {
        return provider === 's3' || provider === 'minio' || provider === 's3-v4-compat'
            || provider === 'exoscale-sos' || provider === 'digitalocean-spaces'
            || provider === 'scaleway-objectstorage' || provider === 'wasabi' || provider === 'backblaze-b2'
            || provider === 'linode-objectstorage' || provider === 'ovh-objectstorage' || provider === 'ionos-objectstorage';
    };

    $scope.mountlike = function (provider) {
        return provider === 'sshfs' || provider === 'cifs' || provider === 'nfs';
    };

    // https://stackoverflow.com/questions/3665115/how-to-create-a-file-in-memory-for-user-to-download-but-not-through-server#18197341
    function download(filename, text) {
        var element = document.createElement('a');
        element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
        element.setAttribute('download', filename);

        element.style.display = 'none';
        document.body.appendChild(element);

        element.click();

        document.body.removeChild(element);
    }

    $scope.downloadConfig = function (backup) {
        // secrets and tokens already come with placeholder characters we remove them
        var tmp = {
            backupId: backup.id,
            encrypted: !!$scope.backupConfig.password  // we add this just to help the import UI
        };

        Object.keys($scope.backupConfig).forEach(function (k) {
            if ($scope.backupConfig[k] !== SECRET_PLACEHOLDER) tmp[k] = $scope.backupConfig[k];
        });

        var filename = 'cloudron-backup-config-' + (new Date).toISOString().replace(/:|T/g,'-').replace(/\..*/,'') + '.json';
        download(filename, JSON.stringify(tmp));
    };

    $scope.backupDetails = {
        backup: null,

        show: function (backup) {
            $scope.backupDetails.backup = backup;
            $('#backupDetailsModal').modal('show');
        }
    };

    $scope.configureScheduleAndRetention = {
        busy: false,
        error: {},

        retentionPolicy:  $scope.retentionPolicies[0],
        days: [],
        hours: [],

        show: function () {
            $scope.configureScheduleAndRetention.error = {};
            $scope.configureScheduleAndRetention.busy = false;

            var selectedPolicy = $scope.retentionPolicies.find(function (x) { return angular.equals(x.value, $scope.backupConfig.retentionPolicy); });
            if (!selectedPolicy) selectedPolicy = $scope.retentionPolicies[0];

            $scope.configureScheduleAndRetention.retentionPolicy = selectedPolicy.value;

            var tmp = $scope.backupConfig.schedulePattern.split(' ');
            var hours = tmp[2].split(','), days = tmp[5].split(',');
            if (days[0] === '*') {
                $scope.configureScheduleAndRetention.days = angular.copy($scope.cronDays, []);
            } else {
                $scope.configureScheduleAndRetention.days = days.map(function (day) { return $scope.cronDays[parseInt(day, 10)]; });
            }
            $scope.configureScheduleAndRetention.hours = hours.map(function (hour) { return $scope.cronHours[parseInt(hour, 10)]; });

            $('#configureScheduleAndRetentionModal').modal('show');
        },

        submit: function () {
            $scope.configureScheduleAndRetention.error = {};
            $scope.configureScheduleAndRetention.busy = true;

            // start with the full backupConfig since the api requires all fields
            var backupConfig = $scope.backupConfig;
            backupConfig.retentionPolicy = $scope.configureScheduleAndRetention.retentionPolicy;

            var daysPattern;
            if ($scope.configureScheduleAndRetention.days.length === 7) daysPattern = '*';
            else daysPattern = $scope.configureScheduleAndRetention.days.map(function (d) { return d.value; });

            var hoursPattern;
            if ($scope.configureScheduleAndRetention.hours.length === 24) hoursPattern = '*';
            else hoursPattern = $scope.configureScheduleAndRetention.hours.map(function (d) { return d.value; });

            backupConfig.schedulePattern ='00 00 ' + hoursPattern + ' * * ' + daysPattern;

            Client.setBackupConfig(backupConfig, function (error) {
                $scope.configureScheduleAndRetention.busy = false;

                if (error) {
                    if (error.statusCode === 424) {
                        $scope.configureScheduleAndRetention.error.generic = error.message;
                    } else if (error.statusCode === 400) {
                        $scope.configureScheduleAndRetention.error.generic = error.message;
                    } else {
                        console.error('Unable to change schedule or retention.', error);
                    }

                    return;
                }

                $('#configureScheduleAndRetentionModal').modal('hide');

                getBackupConfig();
            });
        }
    };

    $scope.configureBackup = {
        busy: false,
        error: {},

        provider: '',
        bucket: '',
        prefix: '',
        accessKeyId: '',
        secretAccessKey: '',
        gcsKey: { keyFileName: '', content: '' },
        region: '',
        endpoint: '',
        backupFolder: '',
        mountPoint: '',
        acceptSelfSignedCerts: false,
        useHardlinks: true,
        externalDisk: false,
        format: 'tgz',
        password: '',
        passwordRepeat: '',
        advancedVisible: false,

        memoryTicks: [],
        memoryLimit: 400 * 1024 * 1024,
        uploadPartSizeTicks: [],
        uploadPartSize: 50 * 1024 * 1024,
        copyConcurrency: '',
        downloadConcurrency: '',
        syncConcurrency: '', // sort of similar to upload

        clearProviderFields: function () {
            $scope.configureBackup.bucket = '';
            $scope.configureBackup.prefix = '';
            $scope.configureBackup.accessKeyId = '';
            $scope.configureBackup.secretAccessKey = '';
            $scope.configureBackup.gcsKey.keyFileName = '';
            $scope.configureBackup.gcsKey.content = '';
            $scope.configureBackup.endpoint = '';
            $scope.configureBackup.region = '';
            $scope.configureBackup.backupFolder = '';
            $scope.configureBackup.mountPoint = '';
            $scope.configureBackup.acceptSelfSignedCerts = false;
            $scope.configureBackup.useHardlinks = true;
            $scope.configureBackup.externalDisk = false;
            $scope.configureBackup.memoryLimit = 400 * 1024 * 1024;

            // scaleway only supports 1000 parts per object (https://www.scaleway.com/en/docs/s3-multipart-upload/)
            $scope.configureBackup.uploadPartSize = $scope.configureBackup.provider === 'scaleway-objectstorage' ? 100 * 1024 * 1024 : 10 * 1024 * 1024;
            $scope.configureBackup.downloadConcurrency = $scope.configureBackup.provider === 's3' ? 30 : 10;
            $scope.configureBackup.syncConcurrency = $scope.configureBackup.provider === 's3' ? 20 : 10;
            $scope.configureBackup.copyConcurrency = $scope.configureBackup.provider === 's3' ? 500 : 10;
        },

        show: function () {
            $scope.configureBackup.error = {};
            $scope.configureBackup.busy = false;

            $scope.configureBackup.advancedVisible = false;

            $scope.configureBackup.provider = $scope.backupConfig.provider;
            $scope.configureBackup.bucket = $scope.backupConfig.bucket;
            $scope.configureBackup.prefix = $scope.backupConfig.prefix;
            $scope.configureBackup.region = $scope.backupConfig.region;
            $scope.configureBackup.accessKeyId = $scope.backupConfig.accessKeyId;
            $scope.configureBackup.secretAccessKey = $scope.backupConfig.secretAccessKey;
            if ($scope.backupConfig.provider === 'gcs') {
                $scope.configureBackup.gcsKey.keyFileName = $scope.backupConfig.credentials.client_email;
                $scope.configureBackup.gcsKey.content = JSON.stringify({
                    project_id: $scope.backupConfig.projectId,
                    client_email: $scope.backupConfig.credentials.client_email,
                    private_key: $scope.backupConfig.credentials.private_key,
                });
            }
            $scope.configureBackup.endpoint = $scope.backupConfig.endpoint;
            $scope.configureBackup.password = $scope.backupConfig.password || '';
            $scope.configureBackup.passwordRepeat = '';
            $scope.configureBackup.backupFolder = $scope.backupConfig.backupFolder;
            $scope.configureBackup.mountPoint = $scope.backupConfig.mountPoint;
            $scope.configureBackup.format = $scope.backupConfig.format;
            $scope.configureBackup.acceptSelfSignedCerts = !!$scope.backupConfig.acceptSelfSignedCerts;
            $scope.configureBackup.useHardlinks = !$scope.backupConfig.noHardlinks;
            $scope.configureBackup.externalDisk = !!$scope.backupConfig.externalDisk;

            $scope.configureBackup.memoryLimit = $scope.backupConfig.memoryLimit;

            $scope.configureBackup.uploadPartSize = $scope.backupConfig.uploadPartSize || ($scope.configureBackup.provider === 'scaleway-objectstorage' ? 100 * 1024 * 1024 : 10 * 1024 * 1024);
            $scope.configureBackup.downloadConcurrency = $scope.backupConfig.downloadConcurrency || ($scope.backupConfig.provider === 's3' ? 30 : 10);
            $scope.configureBackup.syncConcurrency = $scope.backupConfig.syncConcurrency || ($scope.backupConfig.provider === 's3' ? 20 : 10);
            $scope.configureBackup.copyConcurrency = $scope.backupConfig.copyConcurrency || ($scope.backupConfig.provider === 's3' ? 500 : 10);

            var totalMemory = Math.max(($scope.memory.memory + $scope.memory.swap) * 1.5, 2 * 1024 * 1024);
            $scope.configureBackup.memoryTicks = [ 400 * 1024 * 1024 ];
            for (var i = 512; i <= totalMemory/1024/1024; i *= 2) {
                $scope.configureBackup.memoryTicks.push(i * 1024 * 1024);
            }

            $scope.configureBackup.uploadPartSizeTicks = [ 5 * 1024 * 1024 ];
            for (var j = 32; j <= 1 * 1024; j *= 2) { // 5 GB is max for s3. but let's keep things practical for now. we upload 3 parts in parallel
                $scope.configureBackup.uploadPartSizeTicks.push(j * 1024 * 1024);
            }


            $('#configureBackupModal').modal('show');
        },

        submit: function () {
            $scope.configureBackup.error = {};
            $scope.configureBackup.busy = true;

            var backupConfig = {
                provider: $scope.configureBackup.provider,
                format: $scope.configureBackup.format,
                memoryLimit: $scope.configureBackup.memoryLimit,
                // required for api call to provide all fields
                schedulePattern: $scope.backupConfig.schedulePattern,
                retentionPolicy: $scope.backupConfig.retentionPolicy
            };
            if ($scope.configureBackup.password) backupConfig.password = $scope.configureBackup.password;

            // only set provider specific fields, this will clear them in the db
            if ($scope.s3like(backupConfig.provider)) {
                backupConfig.bucket = $scope.configureBackup.bucket;
                backupConfig.prefix = $scope.configureBackup.prefix;
                backupConfig.accessKeyId = $scope.configureBackup.accessKeyId;
                backupConfig.secretAccessKey = $scope.configureBackup.secretAccessKey;

                if ($scope.configureBackup.endpoint) backupConfig.endpoint = $scope.configureBackup.endpoint;

                if (backupConfig.provider === 's3') {
                    if ($scope.configureBackup.region) backupConfig.region = $scope.configureBackup.region;
                    delete backupConfig.endpoint;
                } else if (backupConfig.provider === 'minio' || backupConfig.provider === 's3-v4-compat') {
                    backupConfig.region = $scope.configureBackup.region || 'us-east-1';
                    backupConfig.acceptSelfSignedCerts = $scope.configureBackup.acceptSelfSignedCerts;
                    backupConfig.s3ForcePathStyle = true; // might want to expose this in the UI
                } else if (backupConfig.provider === 'exoscale-sos') {
                    backupConfig.region = 'us-east-1';
                    backupConfig.signatureVersion = 'v4';
                } else if (backupConfig.provider === 'wasabi') {
                    backupConfig.region = $scope.wasabiRegions.find(function (x) { return x.value === $scope.configureBackup.endpoint; }).region;
                    backupConfig.signatureVersion = 'v4';
                } else if (backupConfig.provider === 'scaleway-objectstorage') {
                    backupConfig.region = $scope.scalewayRegions.find(function (x) { return x.value === $scope.configureBackup.endpoint; }).region;
                    backupConfig.signatureVersion = 'v4';
                } else if (backupConfig.provider === 'linode-objectstorage') {
                    backupConfig.region = $scope.linodeRegions.find(function (x) { return x.value === $scope.configureBackup.endpoint; }).region;
                    backupConfig.signatureVersion = 'v4';
                } else if (backupConfig.provider === 'ovh-objectstorage') {
                    backupConfig.region = $scope.ovhRegions.find(function (x) { return x.value === $scope.configureBackup.endpoint; }).region;
                    backupConfig.signatureVersion = 'v4';
                } else if (backupConfig.provider === 'ionos-objectstorage') {
                    backupConfig.region = $scope.ionosRegions.find(function (x) { return x.value === $scope.configureBackup.endpoint; }).region;
                    backupConfig.signatureVersion = 'v4';
                } else if (backupConfig.provider === 'digitalocean-spaces') {
                    backupConfig.region = 'us-east-1';
                }
            } else if (backupConfig.provider === 'gcs') {
                backupConfig.bucket = $scope.configureBackup.bucket;
                backupConfig.prefix = $scope.configureBackup.prefix;
                try {
                    var serviceAccountKey = JSON.parse($scope.configureBackup.gcsKey.content);
                    backupConfig.projectId = serviceAccountKey.project_id;
                    backupConfig.credentials = {
                        client_email: serviceAccountKey.client_email,
                        private_key: serviceAccountKey.private_key
                    };

                    if (!backupConfig.projectId || !backupConfig.credentials || !backupConfig.credentials.client_email || !backupConfig.credentials.private_key) {
                        throw 'fields_missing';
                    }
                } catch (e) {
                    $scope.configureBackup.error.generic = 'Cannot parse Google Service Account Key: ' + e.message;
                    $scope.configureBackup.error.gcsKeyInput = true;
                    $scope.configureBackup.busy = false;
                    return;
                }
            } else if (backupConfig.provider === 'sshfs' || backupConfig.provider === 'cifs' || backupConfig.provider === 'nfs') {
                backupConfig.mountPoint = $scope.configureBackup.mountPoint;
                backupConfig.prefix = $scope.configureBackup.prefix;
                backupConfig.noHardlinks = !$scope.configureBackup.useHardlinks;
            } else if (backupConfig.provider === 'filesystem') {
                backupConfig.backupFolder = $scope.configureBackup.backupFolder;
                backupConfig.noHardlinks = !$scope.configureBackup.useHardlinks;
                backupConfig.externalDisk = $scope.configureBackup.externalDisk;
            }

            backupConfig.uploadPartSize = $scope.configureBackup.uploadPartSize;

            if (backupConfig.format === 'rsync') {
                backupConfig.downloadConcurrency = $scope.configureBackup.downloadConcurrency;
                backupConfig.syncConcurrency = $scope.configureBackup.syncConcurrency;
                backupConfig.copyConcurrency = $scope.configureBackup.copyConcurrency;
            }

            Client.setBackupConfig(backupConfig, function (error) {
                $scope.configureBackup.busy = false;

                if (error) {
                    if (error.statusCode === 424) {
                        $scope.configureBackup.error.generic = error.message;

                        if (error.message.indexOf('AWS Access Key Id') !== -1) {
                            $scope.configureBackup.error.accessKeyId = true;
                            $scope.configureBackup.accessKeyId = '';
                            $scope.configureBackupForm.accessKeyId.$setPristine();
                            $('#inputConfigureBackupAccessKeyId').focus();
                        } else if (error.message.indexOf('not match the signature') !== -1 ) {
                            $scope.configureBackup.error.secretAccessKey = true;
                            $scope.configureBackup.secretAccessKey = '';
                            $scope.configureBackupForm.secretAccessKey.$setPristine();
                            $('#inputConfigureBackupSecretAccessKey').focus();
                        } else if (error.message.toLowerCase() === 'access denied') {
                            $scope.configureBackup.error.bucket = true;
                            $scope.configureBackup.bucket = '';
                            $scope.configureBackupForm.bucket.$setPristine();
                            $('#inputConfigureBackupBucket').focus();
                        } else if (error.message.indexOf('ECONNREFUSED') !== -1) {
                            $scope.configureBackup.error.generic = 'Unknown region';
                            $scope.configureBackup.error.region = true;
                            $scope.configureBackupForm.region.$setPristine();
                            $('#inputConfigureBackupDORegion').focus();
                        } else if (error.message.toLowerCase() === 'wrong region') {
                            $scope.configureBackup.error.generic = 'Wrong S3 Region';
                            $scope.configureBackup.error.region = true;
                            $scope.configureBackupForm.region.$setPristine();
                            $('#inputConfigureBackupS3Region').focus();
                        } else {
                            $('#inputConfigureBackupBucket').focus();
                        }
                    } else if (error.statusCode === 400) {
                        $scope.configureBackup.error.generic = error.message;

                        if (error.message.indexOf('password') !== -1) {
                            $scope.configureBackup.error.password = true;
                            $scope.configureBackupForm.password.$setPristine();
                        } else if ($scope.configureBackup.provider === 'filesystem') {
                            $scope.configureBackup.error.backupFolder = true;
                        }
                    } else {
                        console.error('Unable to change provider.', error);
                    }

                    return;
                }

                // $scope.configureBackup.reset();
                $('#configureBackupModal').modal('hide');

                getBackupConfig();
                checkBackupConfig();
            });
        }
    };

    function fetchBackups() {
        Client.getBackups(function (error, backups) {
            if (error) return console.error(error);

            $scope.backups = backups;
            $scope.backups = $scope.backups.slice(0, 20); // only show 20 since we don't have pagination

            // add contents property
            var appsById = {};
            Client.getInstalledApps().forEach(function (app) { appsById[app.id] = app; });

            $scope.backups.forEach(function (backup) {
                backup.contents = [];
                backup.dependsOn.forEach(function (appBackupId) {
                    let match = appBackupId.match(/app_(.*?)_.*/); // *? means non-greedy
                    if (!match || !appsById[match[1]]) return;
                    backup.contents.push(appsById[match[1]]);
                });
            });
        });
    }

    function getBackupConfig() {
        Client.getBackupConfig(function (error, backupConfig) {
            if (error) return console.error(error);

            $scope.backupConfig = backupConfig;
        });
    }

    function checkBackupConfig() {
        Client.checkBackupConfig(function (error, check) {
            if (error) return console.error(error);

            $scope.backupCheck = check;
        });
    }

    Client.onReady(function () {
        Client.memory(function (error, memory) {
            if (error) console.error(error);

            $scope.memory = memory;

            fetchBackups();
            getBackupConfig();
            checkBackupConfig();

            $scope.manualBackupApps = Client.getInstalledApps().filter(function (app) { return !app.enableBackup; });

            // show backup status
            $scope.createBackup.checkStatus();
        });
    });

    function readFileLocally(obj, file, fileName) {
        return function (event) {
            $scope.$apply(function () {
                obj[file] = null;
                obj[fileName] = event.target.files[0].name;

                var reader = new FileReader();
                reader.onload = function (result) {
                    if (!result.target || !result.target.result) return console.error('Unable to read local file');
                    obj[file] = result.target.result;
                };
                reader.readAsText(event.target.files[0]);
            });
        };
    }

    document.getElementById('gcsKeyFileInput').onchange = readFileLocally($scope.configureBackup.gcsKey, 'content', 'keyFileName');

    // setup all the dialog focus handling
    ['configureBackupModal'].forEach(function (id) {
        $('#' + id).on('shown.bs.modal', function () {
            $(this).find("[autofocus]:first").focus();
        });
    });

    $('.modal-backdrop').remove();
}]);
