/// <summary>
        /// Imports the data sets and returns a map of old ids to new ids.
        /// </summary>
        /// <param name="sessionData">The session data.</param>
        /// <param name="migrationData">Contains the data sets to import.</param>
        /// <param name="validationResults">A list of validation results.</param>
        /// <param name="migrationResults">A list to which import results will be appended.</param>
        /// <returns>A map of data set ids, where the old id is the key and the new id is the value.</returns>
        private Dictionary<string, string> ImportDataSets(SessionData sessionData, MigrationData migrationData, MigrationDataResults validationResults, MigrationDataResults migrationResults)
        {
            Dictionary<string, string> map = new Dictionary<string, string>();
            GenericDataSetList dataSetList = this.manager.GetDataSetList(sessionData.UserId, false);
            foreach (GenericDataSet dataSet in migrationData.DataSets)
            {
                var dataSetId = dataSet.Id;
                if (!validationResults.GetNotificationsFor(dataSet).CanImport)
                {
                    migrationResults.AddNotification(dataSet, MessageType.Information, string.Format(Messages.Import_DataSetSkipped, dataSet.Name));
                    continue;
                }

                // Match on dataset name and overwrite if one exists
                var match = dataSetList.FirstOrDefault(ds => ds.Name == dataSet.Name);
                if (match != null)
                {
                    dataSet.Id = match.Id;
                }

                GenericDataSet savedDataSet = this.manager.SaveDataSet(dataSet, sessionData.UserId);
                migrationResults.AddNotification(savedDataSet, MessageType.Information, string.Format(Messages.Import_DataSetSuccess, savedDataSet.Name));
                map.Add(dataSetId, savedDataSet.Id);
            }

            return map;
        }
        /// <summary>
        /// Perform an import.
        /// </summary>
        /// <param name="sessionData">The session data.</param>
        /// <param name="migrationData">The data to import.</param>
        /// <param name="validator">A validator for validating data prior to import.</param>
        /// <returns>The import process results.</returns>
        public MigrationDataResults Import(SessionData sessionData, MigrationData migrationData, MigrationDataValidator validator)
        {
            MigrationDataResults validationResults = validator.Validate(migrationData);
            MigrationDataResults migrationResults = new MigrationDataResults();

            OrganisationList allOrganisations = this.manager.GetOrganisationList(sessionData.UserId, true);
            Dictionary<string, string> orgMap = this.ImportOrganisations(sessionData.UserId, allOrganisations, migrationData.Organisations, validationResults, migrationResults);
            RoleList allRoles = this.manager.GetRoleList(sessionData.UserId);
            Dictionary<string, string> systemRoleMap = this.ImportRoles(sessionData.UserId, migrationData.Roles, allRoles, validationResults, migrationResults);
            this.ImportUsers(sessionData, migrationData.Users, allRoles, allOrganisations, systemRoleMap, orgMap, validationResults, migrationResults);
            this.ImportProducts(sessionData, migrationData, systemRoleMap, orgMap, validationResults, migrationResults);
            return migrationResults;
        }
        /// <summary>
        /// Imports the service endpoints and returns a map of old ids to new ids.
        /// </summary>
        /// <param name="sessionData">The session data.</param>
        /// <param name="migrationData">Contains the service endpoints to import.</param>
        /// <param name="validationResults">A list of validation results.</param>
        /// <param name="migrationResults">A list to which import results will be appended.</param>
        /// <returns>A map of service endpoint ids, where the old id is the key and the new id is the value.</returns>
        private Dictionary<string, string> ImportServiceEndpoints(SessionData sessionData, MigrationData migrationData, MigrationDataResults validationResults, MigrationDataResults migrationResults)
        {
            Dictionary<string, string> map = new Dictionary<string, string>();
            foreach (ServiceEndpoint endpoint in migrationData.ServiceEndpoints)
            {
                if (!validationResults.GetNotificationsFor(endpoint).CanImport)
                {
                    migrationResults.AddNotification(endpoint, MessageType.Information, string.Format(Messages.Import_ServiceEndpointSkipped, endpoint.Name));
                    continue;
                }

                ServiceEndpoint savedEndpoint;
                try
                {
                    savedEndpoint = this.manager.SaveServiceEndpoint(endpoint, sessionData.UserId);
                }
                catch (ValidationException e)
                {
                    throw new Exception(string.Format(Messages.Validate_ServiceEndpointFailure, endpoint.Name, endpoint.Id, e.Results.First().Message));
                }

                migrationResults.AddNotification(savedEndpoint, MessageType.Information, string.Format(Messages.Import_ServiceEndpointSuccess, savedEndpoint.Name));
                map.Add(endpoint.Id, savedEndpoint.Id);
            }

            return map;
        }
        /// <summary>
        /// Imports the users.
        /// </summary>
        /// <param name="sessionData">The session data.</param>
        /// <param name="users">The list of users to import.</param>
        /// <param name="allRoles">A list of all roles in the system.</param>
        /// <param name="allOrganisations">A list of all organisations in the system.</param>
        /// <param name="systemRoleMap">A map of role ids, where the old id is the key and the new id is the value.</param>
        /// <param name="organisationMap">A map of organisation ids, where the old id is the key and the new id is the value.</param>
        /// <param name="validationResults">A list of validation results.</param>
        /// <param name="migrationResults">A list to which import results will be appended.</param>
        private void ImportUsers(SessionData sessionData, UserList users, RoleList allRoles, OrganisationList allOrganisations, Dictionary<string, string> systemRoleMap, Dictionary<string, string> organisationMap, MigrationDataResults validationResults, MigrationDataResults migrationResults)
        {
            foreach (User user in users)
            {
                if (!validationResults.GetNotificationsFor(user).CanImport)
                {
                    migrationResults.AddNotification(user, MessageType.Information, string.Format(Messages.Import_UserSkipped, user.DisplayName));
                    continue;
                }

                foreach (KeyValuePair<string, string> kvp in systemRoleMap)
                {
                    if (user.Roles.ContainsKey(kvp.Key))
                    {
                        user.Roles.Remove(kvp.Key);
                        user.Roles.Add(kvp.Value, allRoles[kvp.Value].RoleName);
                    }
                }

                foreach (KeyValuePair<string, string> kvp in organisationMap)
                {
                    if (user.Organisations.ContainsKey(kvp.Key))
                    {
                        user.Organisations.Remove(kvp.Key);
                        user.Organisations.Add(kvp.Value, allOrganisations[kvp.Value].Name);
                    }
                }

                user.Id = null;
                User savedUser = this.securityGateway.SaveUser(sessionData, user);
                if (string.IsNullOrEmpty(user.ExternalId))
                {
                    migrationResults.AddNotification(savedUser, MessageType.Information, string.Format(Messages.Import_UserSuccess, savedUser.DisplayName));
                }
                else
                {
                    migrationResults.AddNotification(savedUser, MessageType.Information, string.Format(Messages.Import_UserSuccessNoPasswordChange, savedUser.DisplayName));
                }
            }
        }
        /// <summary>
        /// Imports the roles.
        /// </summary>
        /// <param name="userId">The user making the request.</param>
        /// <param name="roles">The list of roles to import.</param>
        /// <param name="allRoles">A list of all roles already in the system.</param>
        /// <param name="validationResults">A list of validation results.</param>
        /// <param name="migrationResults">A list to which import results will be appended.</param>
        /// <returns>A map of role ids, where the old id is the key and the new id is the value.</returns>
        private Dictionary<string, string> ImportRoles(string userId, RoleList roles, RoleList allRoles, MigrationDataResults validationResults, MigrationDataResults migrationResults)
        {
            Dictionary<string, string> systemRoleMap = new Dictionary<string, string>();
            foreach (Role role in roles)
            {
                if (role.SystemDefined)
                {
                    string mappedValue = allRoles.FirstOrDefault(r => r.RoleName == role.RoleName).Id;
                    systemRoleMap.Add(role.Id, mappedValue);
                    migrationResults.AddNotification(role, MessageType.Information, string.Format(Messages.Import_RoleSkipped, role.RoleName));
                    continue;
                }

                if (!validationResults.GetNotificationsFor(role).CanImport)
                {
                    migrationResults.AddNotification(role, MessageType.Information, string.Format(Messages.Import_RoleSkipped, role.RoleName));
                    continue;
                }

                this.manager.SaveRole(userId, role, true);
                migrationResults.AddNotification(role, MessageType.Information, string.Format(Messages.Import_RoleSuccess, role.RoleName));
            }

            return systemRoleMap;
        }
        /// <summary>
        /// Imports the products.
        /// </summary>
        /// <param name="sessionData">The session data.</param>
        /// <param name="migrationData">Contains the products to import.</param>
        /// <param name="systemRoleMap">A map of role ids, where the old id is the key and the new id is the value.</param>
        /// <param name="organisationMap">A map of organisation ids, where the old id is the key and the new id is the value.</param>
        /// <param name="validationResults">A list of validation results.</param>
        /// <param name="migrationResults">A list to which import results will be appended.</param>
        private void ImportProducts(SessionData sessionData, MigrationData migrationData, Dictionary<string, string> systemRoleMap, Dictionary<string, string> organisationMap, MigrationDataResults validationResults, MigrationDataResults migrationResults)
        {
            const string importedMessage = "Imported via Workbench";
            Dictionary<string, string> validatorMap = this.ImportValidators(sessionData.UserId, migrationData.Validators);
            Dictionary<string, string> serviceEndpointMap = this.ImportServiceEndpoints(sessionData, migrationData, validationResults, migrationResults);
            Dictionary<string, string> dataSetMap = this.ImportDataSets(sessionData, migrationData, validationResults, migrationResults);

            SystemSettings settings = this.manager.GetSystemSettings(sessionData.UserId);
            if (settings.EnableFormApprovals)
            {
                migrationData.Products.ForEach(p => p.BranchName = Constants.DefaultImportBranch);
            }

            foreach (ProductDefinition product in migrationData.Products)
            {
                if (!validationResults.GetNotificationsFor(product).CanImport)
                {
                    continue;
                }

                ProductDataSourceConfiguration dataSourceConfiguration = migrationData.ProductDataSourceConfigurations[product.Id];

                if (organisationMap.ContainsKey(product.OrganisationId))
                {
                    product.OrganisationId = organisationMap[product.OrganisationId];
                }

                WorkflowConfigurationContainer workflowConfiguration = migrationData.WorkflowConfigurations[product.Id];

                ApplicationEntitlementList applicationEntitlements = new ApplicationEntitlementList(migrationData.Security.SelectMany(s => s.ApplicationEntitlements).Where(e => e.ProductId == product.Id));
                PageEntitlementList pageEntitlements = new PageEntitlementList(migrationData.Security.SelectMany(s => s.PageEntitlements).Where(e => e.ProductId == product.Id));
                ControlEntitlementList controlEntitlements = new ControlEntitlementList(migrationData.Security.SelectMany(s => s.ControlEntitlements).Where(e => e.ProductId == product.Id));
                MetadataEntitlementList metadataEntitlements = new MetadataEntitlementList(migrationData.Security.SelectMany(s => s.MetadataEntitlements).Where(e => e.ProductId == product.Id));
                SecurityConfiguration securityConfiguration = new SecurityConfiguration(applicationEntitlements, pageEntitlements, controlEntitlements, metadataEntitlements);

                foreach (KeyValuePair<string, string> kvp in systemRoleMap)
                {
                    foreach (ApplicationEntitlement entitlement in applicationEntitlements.Where(e => e.RoleId == kvp.Key))
                    {
                        entitlement.RoleId = systemRoleMap[entitlement.RoleId];
                    }

                    foreach (PageEntitlement entitlement in pageEntitlements.Where(e => e.RoleId == kvp.Key))
                    {
                        entitlement.RoleId = systemRoleMap[entitlement.RoleId];
                    }

                    foreach (ControlEntitlement entitlement in controlEntitlements.Where(e => e.RoleId == kvp.Key))
                    {
                        entitlement.RoleId = systemRoleMap[entitlement.RoleId];
                    }

                    foreach (MetadataEntitlement entitlement in metadataEntitlements.Where(e => e.RoleId == kvp.Key))
                    {
                        entitlement.RoleId = systemRoleMap[entitlement.RoleId];
                    }

                    foreach (EmailWorkflowTransitionAction emailTransition in workflowConfiguration.Configuration.States.SelectMany(s => s.Transitions).SelectMany(t => t.TransitionActionList).OfType<EmailWorkflowTransitionAction>().Where(ewt => ewt.PdfRenderRole == kvp.Key))
                    {
                        emailTransition.PdfRenderRole = systemRoleMap[emailTransition.PdfRenderRole];
                    }
                }

                string mappedId;

                // Update control validators with new IDs
                IEnumerable<ControlRegexValidator> controlValidators = product.FormDefinition.Pages.AllControls.AllValidators();
                foreach (ControlRegexValidator validator in controlValidators)
                {
                    bool available = validatorMap.TryGetValue(validator.ValidatorId, out mappedId);
                    if (available)
                    {
                        validator.ValidatorId = mappedId;
                    }
                }

                // Update prebuilt option sources with new IDs
                List<ControlWithOptions> prebuiltOptionControls = product.FormDefinition.Pages.AllControls.FindAllRecursive<ControlWithOptions>(x => x.OptionSource.Type == OptionSourceType.Prebuilt);
                foreach (ControlWithOptions prebuiltOptionControl in prebuiltOptionControls)
                {
                    bool available = dataSetMap.TryGetValue(((PrebuiltOptionSource)prebuiltOptionControl.OptionSource).DataSetId, out mappedId);
                    if (available)
                    {
                        ((PrebuiltOptionSource)prebuiltOptionControl.OptionSource).DataSetId = mappedId;
                    }
                }

                // Update service endpoints with new IDs
                this.UpdateServiceEndpoints(product, dataSourceConfiguration, workflowConfiguration.Configuration, serviceEndpointMap);

                product.EditComment = importedMessage;
                ProductVariation productVariation = null;
                if (product.IsChildForm)
                {
                    productVariation = migrationData.ProductVariations.Exists(p => p.ProductId == product.Id)
                        ? migrationData.ProductVariations.First(p => p.ProductId == product.Id)
                        : new ProductVariation
                        {
                            AddedControls = new List<int>(),
                            EditedControls = new List<int>(),
                            RemovedControls = new List<int>()
                        };
                }

                Product savedProduct = this.manager.SaveProduct(sessionData, product, workflowConfiguration.Configuration, securityConfiguration, dataSourceConfiguration, productVariation);
                migrationResults.AddNotification(savedProduct, MessageType.Information, string.Format(Messages.Import_ProductSuccess, savedProduct.Name, savedProduct.Version));
            }
        }
        /// <summary>
        /// Imports the organisations.
        /// </summary>
        /// <param name="userId">The user making the request.</param>
        /// <param name="allOrganisations">All organisations.</param>
        /// <param name="organisations">The list of organisations to import.</param>
        /// <param name="validationResults">A list of validation results.</param>
        /// <param name="migrationResults">A list to which import results will be appended.</param>
        /// <returns>
        /// A map of organisation ids, where the old id is the key and the new id is the value.
        /// </returns>
        private Dictionary<string, string> ImportOrganisations(string userId, OrganisationList allOrganisations, List<MigrationOrganisation> organisations, MigrationDataResults validationResults, MigrationDataResults migrationResults)
        {
            Dictionary<string, string> organisationMap = new Dictionary<string, string>();
            Organisation baseOrg = this.manager.GetBaseOrganisation();
            foreach (MigrationOrganisation org in organisations)
            {
                if (!validationResults.GetNotificationsFor(org).CanImport)
                {
                    migrationResults.AddNotification(org, MessageType.Information, string.Format(Messages.Import_OrganisationSkipped, org.Name));
                    continue;
                }

                string oldId = org.Id;
                org.Id = null;
                if (string.IsNullOrWhiteSpace(org.ParentOrganisationId) || org.BaseOrganisation)
                {
                    organisationMap.Add(oldId, baseOrg.Id);
                    migrationResults.AddNotification(org, MessageType.Information, string.Format(Messages.Import_BaseOrganisation, org.Name));
                }
                else
                {
                    Organisation parent = allOrganisations.FirstOrDefault(o => o.Name == org.ParentOrganisationName);
                    Organisation thisOrg = allOrganisations.FirstOrDefault(o => o.Name == org.Name);
                    if (parent != null)
                    {
                        org.ParentOrganisationId = parent.Id;
                    }

                    if (thisOrg != null)
                    {
                        organisationMap.Add(oldId, thisOrg.Id);
                        migrationResults.AddNotification(org, MessageType.Information, string.Format(Messages.Import_OrganisationSkipped, org.Name));
                    }
                    else
                    {
                        Organisation newOrg = this.manager.SaveOrganisation(org, userId);
                        organisationMap.Add(oldId, newOrg.Id);
                        allOrganisations.Add(newOrg);
                        migrationResults.AddNotification(org, MessageType.Information, string.Format(Messages.Import_OrganisationSuccess, org.Name));
                    }
                }
            }

            return organisationMap;
        }
        /// <summary>
        /// Validates the <paramref name="migrationData"/> and returns the results as a list of messages.
        /// </summary>
        /// <param name="migrationData">The migration data to validate.</param>
        /// <returns>A list of warnings resulting from the validation.</returns>
        public MigrationDataResults Validate(MigrationData migrationData)
        {
            MigrationDataResults results = new MigrationDataResults();

            Version currentSystemVersion = this.dataAccess.GetSystemVersion();
            if (migrationData.SystemVersion != null && currentSystemVersion.CompareTo(migrationData.SystemVersion, 2) != 0)
            {
                string message = currentSystemVersion.CompareTo(migrationData.SystemVersion, 2) > 0 ?
                    string.Format(Messages.Validate_TargetSystemVersionTooHigh, currentSystemVersion.ToString(2), migrationData.SystemVersion.ToString(2)) :
                    string.Format(Messages.Validate_TargetSystemVersionTooLow, currentSystemVersion.ToString(2), migrationData.SystemVersion.ToString(2));
                throw new MigrationVersionException(message);
            }

            if (migrationData.SystemVersion == null)
            {
                string message = string.Format(Messages.Validate_ExportMissingVersion, currentSystemVersion.ToString(2));
                throw new MigrationVersionException(message);
            }

            this.ValidateProducts(migrationData, results);

            if (migrationData.Users.Count > 0)
            {
                IEnumerable<string> usernames = migrationData.Users.Select(p => p.Username);
                OrganisationList orgList = this.dataAccess.GetOrganisationList();
                UserSearchCriteria filter = new UserSearchCriteria
                                        {
                                            OrganisationIds = orgList.Select(x => x.Id).ToList(),
                                            Usernames = usernames.ToList()
                                        };
                UserList foundUsers = this.dataAccess.FindUsers(filter);
                foreach (User u in foundUsers)
                {
                    results.AddNotification(migrationData.Users.FirstOrDefault(user => user.Username == u.Username), MessageType.Error, string.Format(Messages.Validate_UserExistsUsername, u.Username));
                }
            }

            if (migrationData.Roles.Count > 0)
            {
                RoleList allRoles = this.dataAccess.GetRoleList();
                foreach (Role r in allRoles.Where(r => !r.SystemDefined))
                {
                    if (migrationData.Roles.Contains(r.Id))
                    {
                        results.AddNotification(r, MessageType.Error, string.Format(Messages.Validate_RoleExistsId, r.RoleName));
                    }
                }
            }

            if (migrationData.Organisations.Count > 0)
            {
                OrganisationList allOrganisations = this.dataAccess.GetOrganisationList();
                foreach (Organisation org in allOrganisations)
                {
                    MigrationOrganisation migrationOrgMatchId = migrationData.Organisations.FirstOrDefault(o => o.Id == org.Id);
                    if (migrationOrgMatchId != null)
                    {
                        results.AddNotification(migrationOrgMatchId, MessageType.Error, string.Format(Messages.Validate_OrgExistsId, org.Name));
                    }
                    else
                    {
                        MigrationOrganisation migrationOrgMatchName = migrationData.Organisations.FirstOrDefault(o => o.Name == org.Name);
                        if (migrationOrgMatchName != null)
                        {
                            results.AddNotification(migrationOrgMatchName, MessageType.Warning, string.Format(Messages.Validate_OrgExistsId, org.Name));
                        }
                    }
                }
            }

            if (migrationData.ServiceEndpoints.Count > 0)
            {
                ServiceEndpointList allEndpoints = this.dataAccess.GetServiceEndpointList();
                foreach (ServiceEndpoint endpoint in allEndpoints)
                {
                    if (migrationData.ServiceEndpoints.Contains(endpoint.Id))
                    {
                        results.AddNotification(endpoint, MessageType.Error, string.Format(Messages.Validate_ServiceEndpointExistsId, endpoint.Name, endpoint.Id));
                    }
                }
            }

            if (migrationData.DataSets.Count > 0)
            {
                GenericDataSetList allDataSets = this.dataAccess.GetDataSetList(false);
                foreach (GenericDataSet dataSet in allDataSets)
                {
                    if (migrationData.DataSets.Any(x => x.Id == dataSet.Id))
                    {
                        results.AddNotification(dataSet, MessageType.Warning, string.Format(Messages.Validate_DataSetExistsId, dataSet.Name, dataSet.Id));
                    }
                }
            }

            return results;
        }
        /// <summary>
        /// Validates the products.
        /// </summary>
        /// <param name="migrationData">The migration data.</param>
        /// <param name="results">The results.</param>
        private void ValidateProducts(MigrationData migrationData, MigrationDataResults results)
        {
            foreach (ProductDefinition p in migrationData.Products)
            {
                if (!migrationData.ProductDataSourceConfigurations.ContainsKey(p.Id))
                {
                    results.AddNotification(p, MessageType.Error, string.Format(Messages.Validate_ProductMissingDataSourceConfig, p.Name));
                }

                if (migrationData.Security.SelectMany(s => s.ApplicationEntitlements).All(e => e.ProductId != p.Id))
                {
                    results.AddNotification(p, MessageType.Error, string.Format(Messages.Validate_ProductMissingSecurityConfig, p.Name));
                }

                if (!migrationData.WorkflowConfigurations.Contains(p.Id))
                {
                    results.AddNotification(p, MessageType.Error, string.Format(Messages.Validate_ProductMissingWorkflowConfig, p.Name));
                }

                if (!results.GetNotificationsFor(p).CanImport)
                {
                    continue;
                }

                try
                {
                    Product foundProduct = this.dataAccess.GetProduct<ProductSearchResult>(p.Id);
                    results.AddNotification(p, MessageType.Warning, string.Format(Messages.Validate_ProductWillBeOverriden, migrationData.Products[p.Id].Name, foundProduct.Name));
                }
                catch (NullReferenceException)
                {
                    // Product does not already exist.
                }
            }
        }