'use strict';

/* global async */
/* global angular */
/* global $, TASK_TYPES */

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

    $scope.config = Client.getConfig();
    $scope.domains = [];
    $scope.ready = false;

    $scope.translationLinks = {
        linodeDocsLink: 'https://docs.cloudron.io/domains/#linode-dns',
        customCertLink: 'https://docs.cloudron.io/certificates/#custom-certificates'
    };

    $scope.openSubscriptionSetup = function () {
        Client.openSubscriptionSetup($scope.$parent.subscription);
    };

    // currently, validation of wildcard with various provider is done server side
    $scope.tlsProvider = [
        { name: 'Let\'s Encrypt Prod', value: 'letsencrypt-prod' },
        { name: 'Let\'s Encrypt Prod - Wildcard', value: 'letsencrypt-prod-wildcard' },
        { name: 'Let\'s Encrypt Staging', value: 'letsencrypt-staging' },
        { name: 'Let\'s Encrypt Staging - Wildcard', value: 'letsencrypt-staging-wildcard' },
        { name: 'Custom Wildcard Certificate', value: 'fallback' },
    ];

    // keep in sync with setupdns.js
    $scope.dnsProvider = [
        { name: 'AWS Route53', value: 'route53' },
        { name: 'Cloudflare', value: 'cloudflare' },
        { name: 'DigitalOcean', value: 'digitalocean' },
        { name: 'Gandi LiveDNS', value: 'gandi' },
        { name: 'GoDaddy', value: 'godaddy' },
        { name: 'Google Cloud DNS', value: 'gcdns' },
        { name: 'Linode', value: 'linode' },
        { name: 'Name.com', value: 'namecom' },
        { name: 'Namecheap', value: 'namecheap' },
        { name: 'Netcup', value: 'netcup' },
        { name: 'Wildcard', value: 'wildcard' },
        { name: 'Manual (not recommended)', value: 'manual' },
        { name: 'No-op (only for development)', value: 'noop' }
    ];

    $scope.prettyProviderName = function (domain) {
        switch (domain.provider) {
        case 'route53': return 'AWS Route53';
        case 'cloudflare': return 'Cloudflare';
        case 'digitalocean': return 'DigitalOcean';
        case 'gandi': return 'Gandi LiveDNS';
        case 'linode': return 'Linode';
        case 'namecom': return 'Name.com';
        case 'namecheap': return 'Namecheap';
        case 'netcup': return 'Netcup';
        case 'gcdns': return 'Google Cloud';
        case 'godaddy': return 'GoDaddy';
        case 'manual': return 'Manual';
        case 'wildcard': return 'Wildcard';
        case 'noop': return 'No-op';
        default: return 'Unknown';
        }
    };

    $scope.needsPort80 = function (dnsProvider, tlsProvider) {
        return ((dnsProvider === 'manual' || dnsProvider === 'noop' || dnsProvider === 'wildcard') &&
            (tlsProvider === 'letsencrypt-prod' || tlsProvider === 'letsencrypt-staging'));
    };

    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]);
            });
        };
    }

    function refreshDomains(callback) {
        var domains = [ ];

        Client.getDomains(function (error, results) {
            if (error) return console.error(error);

            async.eachSeries(results, function (result, iteratorDone) {
                Client.getDomain(result.domain, function (error, domain) {
                    if (error) return iteratorDone(error);

                    domains.push(domain);

                    iteratorDone();
                });
            }, function (error) {
                angular.copy(domains, $scope.domains);

                $scope.changeDashboard.selectedDomain = $scope.changeDashboard.adminDomain = $scope.domains.find(function (d) { return d.domain === $scope.config.adminDomain; });

                if (error) console.error(error);
                if (callback) callback(error);
            });
        });
    }

    $scope.domainAdd = {
        show: function () {
            if ($scope.config.features.domainMaxCount && $scope.config.features.domainMaxCount <= $scope.domains.length) {
                $('#subscriptionRequiredModal').modal('show');
                return;
            }

            $scope.domainConfigure.show();
        }
    };

    // We reused configure also for adding domains to avoid much code duplication
    $scope.domainConfigure = {
        adding: false,
        error: null,
        busy: false,
        domain: null,
        advancedVisible: false,

        // form model
        newDomain: '',
        accessKeyId: '',
        secretAccessKey: '',
        gcdnsKey: { keyFileName: '', content: '' },
        digitalOceanToken: '',
        gandiApiKey: '',
        godaddyApiKey: '',
        godaddyApiSecret: '',
        cloudflareToken: '',
        cloudflareEmail: '',
        cloudflareTokenType: 'GlobalApiKey',
        linodeToken: '',
        nameComToken: '',
        nameComUsername: '',
        namecheapUsername: '',
        namecheapApiKey: '',
        netcupCustomerNumber: '',
        netcupApiKey: '',
        netcupApiPassword: '',
        provider: 'route53',
        zoneName: '',

        mastodonHostname: '',
        matrixHostname: '',

        tlsConfig: {
            provider: 'letsencrypt-prod-wildcard'
        },

        fallbackCert: {
            certificateFile: null,
            certificateFileName: '',
            keyFile: null,
            keyFileName: ''
        },

        setDefaultTlsProvider: function () {
            var dnsProvider = $scope.domainConfigure.provider;
            // wildcard LE won't work without automated DNS
            if (dnsProvider === 'manual' || dnsProvider === 'noop' || dnsProvider === 'wildcard') {
                $scope.domainConfigure.tlsConfig.provider = 'letsencrypt-prod';
            } else {
                $scope.domainConfigure.tlsConfig.provider = 'letsencrypt-prod-wildcard';
            }
        },

        show: function (domain) {
            $scope.domainConfigure.reset();

            if (domain) {
                $scope.domainConfigure.domain = domain;
                $scope.domainConfigure.accessKeyId = domain.config.accessKeyId;
                $scope.domainConfigure.secretAccessKey = domain.config.secretAccessKey;

                $scope.domainConfigure.gcdnsKey.keyFileName = '';
                $scope.domainConfigure.gcdnsKey.content = '';
                if (domain.provider === 'gcdns') {
                    $scope.domainConfigure.gcdnsKey.keyFileName = domain.config.credentials && domain.config.credentials.client_email;

                    $scope.domainConfigure.gcdnsKey.content = JSON.stringify({
                        project_id: domain.config.projectId,
                        client_email: domain.config.credentials.client_email,
                        private_key: domain.config.credentials.private_key
                    });
                }
                $scope.domainConfigure.digitalOceanToken = domain.provider === 'digitalocean' ? domain.config.token : '';
                $scope.domainConfigure.linodeToken = domain.provider === 'linode' ? domain.config.token : '';
                $scope.domainConfigure.gandiApiKey = domain.provider === 'gandi' ? domain.config.token : '';
                $scope.domainConfigure.cloudflareToken = domain.provider === 'cloudflare' ? domain.config.token : '';
                $scope.domainConfigure.cloudflareEmail = domain.provider === 'cloudflare' ? domain.config.email : '';
                $scope.domainConfigure.cloudflareTokenType = domain.provider === 'cloudflare' ? domain.config.tokenType : 'GlobalApiKey';

                $scope.domainConfigure.godaddyApiKey = domain.provider === 'godaddy' ? domain.config.apiKey : '';
                $scope.domainConfigure.godaddyApiSecret = domain.provider === 'godaddy' ? domain.config.apiSecret : '';

                $scope.domainConfigure.nameComToken = domain.provider === 'namecom' ? domain.config.token : '';
                $scope.domainConfigure.nameComUsername = domain.provider === 'namecom' ? domain.config.username : '';

                $scope.domainConfigure.namecheapApiKey = domain.provider === 'namecheap' ? domain.config.token : '';
                $scope.domainConfigure.namecheapUsername = domain.provider === 'namecheap' ? domain.config.username : '';

                $scope.domainConfigure.netcupCustomerNumber = domain.provider === 'netcup' ? domain.config.customerNumber : '';
                $scope.domainConfigure.netcupApiKey = domain.provider === 'netcup' ? domain.config.apiKey : '';
                $scope.domainConfigure.netcupApiPassword = domain.provider === 'netcup' ? domain.config.apiPassword : '';

                $scope.domainConfigure.provider = domain.provider;

                $scope.domainConfigure.tlsConfig.provider = domain.tlsConfig.provider;
                if (domain.tlsConfig.provider.indexOf('letsencrypt') === 0) {
                    if (domain.tlsConfig.wildcard) $scope.domainConfigure.tlsConfig.provider += '-wildcard';
                }
                $scope.domainConfigure.zoneName = domain.zoneName;


                $scope.domainConfigure.matrixHostname = '';
                $scope.domainConfigure.mastodonHostname = '';

                try {
                    if (domain.wellKnown && domain.wellKnown['matrix/server']) {
                        $scope.domainConfigure.matrixHostname = JSON.parse(domain.wellKnown['matrix/server'])['m.server'];
                    }
                    if (domain.wellKnown && domain.wellKnown['host-meta']) {
                        $scope.domainConfigure.mastodonHostname = domain.wellKnown['host-meta'].match(new RegExp('template="https://(.*?)/'))[1];
                    }
                } catch (e) {
                    console.error(e);
                }
            } else {
                $scope.domainConfigure.adding = true;
            }

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

        submit: function () {
            $scope.domainConfigure.busy = true;
            $scope.domainConfigure.error = null;

            var provider = $scope.domainConfigure.provider;

            var data = {};

            if (provider === 'route53') {
                data.accessKeyId = $scope.domainConfigure.accessKeyId;
                data.secretAccessKey = $scope.domainConfigure.secretAccessKey;
            } else if (provider === 'gcdns') {
                try {
                    var serviceAccountKey = JSON.parse($scope.domainConfigure.gcdnsKey.content);
                    data.projectId = serviceAccountKey.project_id;
                    data.credentials = {
                        client_email: serviceAccountKey.client_email,
                        private_key: serviceAccountKey.private_key
                    };

                    if (!data.projectId || !data.credentials || !data.credentials.client_email || !data.credentials.private_key) {
                        throw new Error('One or more fields are missing in the JSON');
                    }
                } catch (e) {
                    $scope.domainConfigure.error = 'Cannot parse Google Service Account Key: ' + e.message;
                    $scope.domainConfigure.busy = false;
                    return;
                }
            } else if (provider === 'digitalocean') {
                data.token = $scope.domainConfigure.digitalOceanToken;
            } else if (provider === 'linode') {
                data.token = $scope.domainConfigure.linodeToken;
            } else if (provider === 'gandi') {
                data.token = $scope.domainConfigure.gandiApiKey;
            } else if (provider === 'godaddy') {
                data.apiKey = $scope.domainConfigure.godaddyApiKey;
                data.apiSecret = $scope.domainConfigure.godaddyApiSecret;
            } else if (provider === 'cloudflare') {
                data.token = $scope.domainConfigure.cloudflareToken;
                data.email = $scope.domainConfigure.cloudflareEmail;
                data.tokenType = $scope.domainConfigure.cloudflareTokenType;
            } else if (provider === 'namecom') {
                data.token = $scope.domainConfigure.nameComToken;
                data.username = $scope.domainConfigure.nameComUsername;
            } else if (provider === 'namecheap') {
                data.token = $scope.domainConfigure.namecheapApiKey;
                data.username = $scope.domainConfigure.namecheapUsername;
            } else if (provider === 'netcup') {
                data.customerNumber = $scope.domainConfigure.netcupCustomerNumber;
                data.apiKey = $scope.domainConfigure.netcupApiKey;
                data.apiPassword = $scope.domainConfigure.netcupApiPassword;
            }

            var fallbackCertificate = null;
            if ($scope.domainConfigure.fallbackCert.certificateFile && $scope.domainConfigure.fallbackCert.keyFile) {
                fallbackCertificate = {
                    cert: $scope.domainConfigure.fallbackCert.certificateFile,
                    key: $scope.domainConfigure.fallbackCert.keyFile
                };
            }

            var tlsConfig = {
                provider: $scope.domainConfigure.tlsConfig.provider,
                wildcard: false
            };
            if ($scope.domainConfigure.tlsConfig.provider.indexOf('-wildcard') !== -1) {
                tlsConfig.provider = tlsConfig.provider.replace('-wildcard', '');
                tlsConfig.wildcard = true;
            }

            var wellKnown = {};
            if ($scope.domainConfigure.matrixHostname) {
                wellKnown['matrix/server'] = JSON.stringify({ 'm.server': $scope.domainConfigure.matrixHostname });
            }
            if ($scope.domainConfigure.mastodonHostname) {
                wellKnown['host-meta'] = '<?xml version="1.0" encoding="UTF-8"?>\n'
                    + '<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">\n'
                    + '<Link rel="lrdd" type="application/xrd+xml" template="https://' + $scope.domainConfigure.mastodonHostname + '/.well-known/webfinger?resource={uri}"/>\n'
                    + '</XRD>';
            }

            // choose the right api, since we reuse this for adding and configuring domains
            var func;
            if ($scope.domainConfigure.adding) func = Client.addDomain.bind(Client, $scope.domainConfigure.newDomain, $scope.domainConfigure.zoneName, provider, data, fallbackCertificate, tlsConfig, wellKnown);
            else func = Client.updateDomain.bind(Client, $scope.domainConfigure.domain.domain, $scope.domainConfigure.zoneName, provider, data, fallbackCertificate, tlsConfig, wellKnown);

            func(function (error) {
                $scope.domainConfigure.busy = false;
                if (error) {
                    $scope.domainConfigure.error = error.message;
                    return;
                }

                $('#domainConfigureModal').modal('hide');
                $scope.domainConfigure.reset();

                refreshDomains();
            });
        },

        reset: function () {
            $scope.domainConfigure.adding = false;
            $scope.domainConfigure.advancedVisible = false;
            $scope.domainConfigure.newDomain = '';

            $scope.domainConfigure.busy = false;
            $scope.domainConfigure.error = null;

            $scope.domainConfigure.provider = '';
            $scope.domainConfigure.accessKeyId = '';
            $scope.domainConfigure.secretAccessKey = '';
            $scope.domainConfigure.gcdnsKey.keyFileName = '';
            $scope.domainConfigure.gcdnsKey.content = '';
            $scope.domainConfigure.digitalOceanToken = '';
            $scope.domainConfigure.gandiApiKey = '';
            $scope.domainConfigure.godaddyApiKey = '';
            $scope.domainConfigure.godaddyApiSecret = '';
            $scope.domainConfigure.cloudflareToken = '';
            $scope.domainConfigure.cloudflareEmail = '';
            $scope.domainConfigure.cloudflareTokenType = 'GlobalApiKey';
            $scope.domainConfigure.nameComToken = '';
            $scope.domainConfigure.nameComUsername = '';
            $scope.domainConfigure.namecheapApiKey = '';
            $scope.domainConfigure.namecheapUsername = '';
            $scope.domainConfigure.netcupCustomerNumber = '';
            $scope.domainConfigure.netcupApiKey = '';
            $scope.domainConfigure.netcupApiPassword = '';

            $scope.domainConfigure.tlsConfig.provider = 'letsencrypt-prod';
            $scope.domainConfigure.zoneName = '';

            $scope.domainConfigureForm.$setPristine();
            $scope.domainConfigureForm.$setUntouched();
        }
    };

    $scope.renewCerts = {
        busy: false,
        percent: 0,
        message: '',
        errorMessage: '',
        taskId: '',

        checkStatus: function () {
            Client.getLatestTaskByType(TASK_TYPES.TASK_RENEW_CERTS, function (error, task) {
                if (error) return console.error(error);

                if (!task) return;

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

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

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

                    return;
                }

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

        renew: function () {
            $scope.renewCerts.busy = true;
            $scope.renewCerts.percent = 0;
            $scope.renewCerts.message = '';
            $scope.renewCerts.errorMessage = '';

            Client.renewCerts(function (error, taskId) {
                if (error) {
                    console.error(error);
                    $scope.renewCerts.errorMessage = error.message;

                    $scope.renewCerts.busy = false;
                } else {
                    $scope.renewCerts.taskId = taskId;
                    $scope.renewCerts.updateStatus();
                }
            });
        }
    };

    $scope.syncDns = {
        busy: false,
        percent: 0,
        message: '',
        errorMessage: '',
        taskId: '',

        checkStatus: function () {
            Client.getLatestTaskByType(TASK_TYPES.TASK_SYNC_DNS_RECORDS, function (error, task) {
                if (error) return console.error(error);

                if (!task) return;

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

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

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

                    return;
                }

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

        sync: function () {
            $scope.syncDns.busy = true;
            $scope.syncDns.percent = 0;
            $scope.syncDns.message = '';
            $scope.syncDns.errorMessage = '';

            Client.setDnsRecords({}, function (error, taskId) {
                if (error) {
                    console.error(error);
                    $scope.syncDns.errorMessage = error.message;

                    $scope.syncDns.busy = false;
                } else {
                    $scope.syncDns.taskId = taskId;
                    $scope.syncDns.updateStatus();
                }
            });
        }
    };

    $scope.domainRemove = {
        busy: false,
        error: null,
        domain: null,

        show: function (domain) {
            $scope.domainRemove.reset();

            $scope.domainRemove.domain = domain;

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

        submit: function () {
            $scope.domainRemove.busy = true;
            $scope.domainRemove.error = null;

            Client.removeDomain($scope.domainRemove.domain.domain, function (error) {
                if (error && (error.statusCode === 403 || error.statusCode === 409)) {
                    $scope.domainRemove.error = error.message;
                } else if (error) {
                    Client.error(error);
                } else {
                    $('#domainRemoveModal').modal('hide');
                    $scope.domainRemove.reset();

                    refreshDomains();
                }

                $scope.domainRemove.busy = false;
            });
        },

        reset: function () {
            $scope.domainRemove.busy = false;
            $scope.domainRemove.error = null;
            $scope.domainRemove.domain = null;
        }
    };

    $scope.changeDashboard = {
        busy: false,
        percent: 0,
        message: '',
        errorMessage: '',
        taskId: '',
        selectedDomain: null,
        adminDomain: null,

        stop: function () {
            Client.stopTask($scope.changeDashboard.taskId, function (error) {
                if (error) console.error(error);
                $scope.changeDashboard.busy = false;
            });
        },

        // this function is not called intentionally. currently, we do switching in two steps - prepare and set
        // if the user refreshed the UI in the middle of prepare, then it would be awkward to resume/call 'set' when the
        // user visits the UI the next time around.
        checkStatus: function () {
            Client.getLatestTaskByType('prepareDashboardDomain', function (error, task) {
                if (error) return console.error(error);
                if (!task) return;

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

        updateStatus: function () {
            if (!$scope.changeDashboard.busy) return; // task got stopped

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

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

                    if (!$scope.changeDashboard.errorMessage) $scope.changeDashboard.setDashboardDomain();

                    return;
                }

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

        setDashboardDomain: function () {
            Client.setDashboardDomain($scope.changeDashboard.selectedDomain.domain, function (error) {
                if (error) {
                    console.error(error);
                    $scope.changeDashboard.errorMessage = error.message;

                    $scope.changeDashboard.busy = false;
                } else {
                    window.location.href = 'https://my.' + $scope.changeDashboard.selectedDomain.domain;
                }
            });
        },

        change: function () {
            $scope.changeDashboard.busy = true;
            $scope.changeDashboard.message = 'Preparing dashboard domain';
            $scope.changeDashboard.percent = 0;
            $scope.changeDashboard.errorMessage = '';

            Client.prepareDashboardDomain($scope.changeDashboard.selectedDomain.domain, function (error, taskId) {
                if (error) {
                    console.error(error);
                    $scope.changeDashboard.errorMessage = error.message;

                    $scope.changeDashboard.busy = false;
                } else {
                    $scope.changeDashboard.taskId = taskId;
                    $scope.changeDashboard.updateStatus();
                }
            });
        }
    };

    Client.onReady(function () {
        refreshDomains(function (error) {
            if (error) return console.error(error);

            $scope.ready = true;
        });

        $scope.renewCerts.checkStatus();
    });

    document.getElementById('gcdnsKeyFileInput').onchange = readFileLocally($scope.domainConfigure.gcdnsKey, 'content', 'keyFileName');
    document.getElementById('fallbackCertFileInput').onchange = readFileLocally($scope.domainConfigure.fallbackCert, 'certificateFile', 'certificateFileName');
    document.getElementById('fallbackKeyFileInput').onchange = readFileLocally($scope.domainConfigure.fallbackCert, 'keyFile', 'keyFileName');

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

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