async public Task MigrateCatolgItems(string sourceTitleID, string targetTitleID)
        {
            //TODO: Make this support multiple catalogs
            var console = new ConsoleTaskWriter("# Migrating Catalog Items. Main Catalog only");

            console.LogProcess("Fetching data");
            var catalogItems = await PlayFabService.GetCatalogData(sourceTitleID);

            if (catalogItems == null)
            {
                console.LogError("Error Fetching CloudScript Data, skipping");
                return;
            }

            if (catalogItems.Count == 0)
            {
                console.LogSuccess("Found no catalog items to update");
                return;
            }

            console.LogProcess("Migrating");
            var success = await PlayFabService.UpdateCatalogData(targetTitleID, catalogItems[0].CatalogVersion, true, catalogItems);

            if (!success)
            {
                console.LogError("Save Catalog Failed, skipping.");
                return;
            }
            console.LogSuccess("Completed migration of catalog items");
        }
        async public Task MigrateTitleData(string sourceTitleID, string targetTitleID)
        {
            var console = new ConsoleTaskWriter("# Migrating TitlData");

            // - FETCH
            console.LogProcess("Fetching data for comparison");

            PlayFab.ServerModels.GetTitleDataResult[] results = await Task.WhenAll(
                PlayFabService.GetTitleData(sourceTitleID),
                PlayFabService.GetTitleData(targetTitleID)
                );

            Dictionary <string, string> sourceTitleData = results[0].Data ?? new Dictionary <string, string>();
            Dictionary <string, string> targetTitleData = results[1].Data ?? new Dictionary <string, string>();

            if (sourceTitleData == null && targetTitleData == null)
            {
                console.LogError("Failed to retrieve title data, continuing...");
            }

            // - UPDATE

            Dictionary <string, string> itemsNeedingUpdate = FilterTitleDataToUpdate(sourceTitleData, targetTitleData);

            if (itemsNeedingUpdate.Count == 0)
            {
                console.LogSuccess("Found no title data items to update.");
                return;
            }

            int totalItems       = itemsNeedingUpdate.Count;
            int updatItemCounter = 0;

            while (itemsNeedingUpdate.Count > 0)
            {
                updatItemCounter++;
                console.LogProcess("Updating " + updatItemCounter + " out of " + totalItems + " items.");

                var kvp = itemsNeedingUpdate.FirstOrDefault();
                itemsNeedingUpdate.Remove(kvp.Key);
                bool success = await PlayFabService.UpdateTitleData(targetTitleID, kvp);

                if (!success)
                {
                    console.LogError("Save Title Data Failed, skipping");
                }
            }

            console.LogSuccess("TitleData Migration completed, Updated " + totalItems + " items");
        }
        async public Task MigrateStores(string sourceTitleID, string targetTitleID, List <String> storeList)
        {
            var console = new ConsoleTaskWriter("# Migrating stores from settings. StoreIDs=" + string.Join(",", storeList.ToArray()));

            if (storeList.Count == 0)
            {
                console.LogError("No stores have been set with SetStores. Skipping migration.");
                return;
            }

            // TODO: Remove any prevoius stores that has been deleted.

            var storeListBufffer = storeList.ToList <string>();

            while (storeListBufffer.Count > 0)
            {
                console.LogProcess("Migrating store");

                var currentStoreId = storeListBufffer[0];
                storeListBufffer.Remove(currentStoreId);
                var result = await PlayFabService.GetStoreData(sourceTitleID, currentStoreId);

                if (result == null)
                {
                    console.LogError("Error Fetching Store Data, trying next store.");
                    continue;
                }

                bool success = await PlayFabService.UpdateStoreData(targetTitleID, currentStoreId, result.CatalogVersion, result.MarketingData, result.Store);

                if (!success)
                {
                    console.LogError("Save Store Failed, trying next store.");
                    continue;
                }
                console.LogProcess("store migrated");
            }

            console.LogSuccess("Completed migration of stores.");
        }
        async public Task MigrateCloudScriptAsync(string sourceTitleID, string targetTitleID)
        {
            var console = new ConsoleTaskWriter("# Migrating CloudScript Data");
            var lists   = await PlayFabService.GetCloudScript(sourceTitleID);

            if (lists == null)
            {
                console.LogError("Failed to fetch CloudScript Data.");
                return;
            }

            console.LogProcess("Migrating script");
            bool success = await PlayFabService.UpdateCloudScript(targetTitleID, lists);

            if (!success)
            {
                console.LogError("Save CloudScript Failed.");
                return;
            }

            console.LogSuccess("Completed cloud script migration.");
        }
        // Overwrites any table with the same id.
        // NOTE: Could not find a way to delete the table that have been created
        // TODO: Make this support multiple catalogs
        async public Task MigrateDropTables(string sourceTitleID, string targetTitleID)
        {
            var console = new ConsoleTaskWriter("# Migrating Drop Table Data, Main Catalog only");

            console.LogProcess("Fetching data");

            Dictionary <string, RandomResultTableListing>[] results = await Task.WhenAll(
                PlayFabService.GetDropTableData(sourceTitleID),
                PlayFabService.GetDropTableData(targetTitleID)
                );

            Dictionary <string, RandomResultTableListing> sourceCatalog = results[0];
            Dictionary <string, RandomResultTableListing> targetCatalog = results[1];

            if (sourceCatalog == null)
            {
                console.LogError("Error Fetching CloudScript Data, skipping");
                return;
            }

            // Find out if the targetTitle has drop tables that the source dosent have
            // at the time of writing there where no PlayFAbAPI methods for deletion
            // The user has to manually go in in the dashboard and delet any unwanted droptables.
            string        divergentMessage = "";
            List <string> deletionKeys     = new List <string>();

            foreach (KeyValuePair <string, RandomResultTableListing> targetTableItem in targetCatalog)
            {
                if (!sourceCatalog.ContainsKey(targetTableItem.Key))
                {
                    deletionKeys.Add(targetTableItem.Key);
                }
            }
            if (deletionKeys.Count > 0)
            {
                divergentMessage = "The target title contains " + deletionKeys.Count +
                                   " items that the source doesnt have. TableIds: " + string.Join(",", deletionKeys.ToArray()) + "." +
                                   " \n If you you want to delete these, you have to do it through the dashboard.";
            }

            if (sourceCatalog.Count <= 0)
            {
                console.LogProcess("Found no drop table to migrate, skipping. ");
                console.ReportError(divergentMessage);
                return;
            }

            List <RandomResultTable> dropTables = new List <RandomResultTable>();

            foreach (RandomResultTableListing item in sourceCatalog.Values)
            {
                RandomResultTable dropTable = new RandomResultTable();
                dropTable.TableId = item.TableId;
                dropTable.Nodes   = item.Nodes;
                dropTables.Add(dropTable);
            }

            console.LogProcess("Migrating data");
            bool success = await PlayFabService.UpdateDropTableData(targetTitleID, dropTables);

            if (!success)
            {
                console.LogError("Error Fetching CloudScript Data, skipping");
                console.ReportError(divergentMessage);
                return;
            }

            console.LogSuccess("Completed Drop Table migration. ");
            console.ReportError(divergentMessage);
        }
        async public Task MigrateCurrencyAsync(string sourceTitleID, string targetTitleID, bool forceOverWrite = true)
        {
            var console = new ConsoleTaskWriter("# Migrating currency data");

            // - FETCH

            // Get data from both titles for comparison
            ListVirtualCurrencyTypesResult[] results = await Task.WhenAll(
                PlayFabService.GetCurrencyData(sourceTitleID),
                PlayFabService.GetCurrencyData(targetTitleID)
                );

            List <VirtualCurrencyData> sourceData = results[0].VirtualCurrencies ?? new List <VirtualCurrencyData>();
            List <VirtualCurrencyData> targetData = results[1].VirtualCurrencies ?? new List <VirtualCurrencyData>();

            // - DELETE

            // Find all items in the target that don't exist in the source
            List <VirtualCurrencyData> dataToBeDeleted = targetData.FindAll((PlayFab.AdminModels.VirtualCurrencyData targetCurrency) => {
                var delete = true;
                foreach (VirtualCurrencyData sourceCurrency in sourceData)
                {
                    if (sourceCurrency.CurrencyCode == targetCurrency.CurrencyCode)
                    {
                        delete = false;
                    }
                }
                return(delete);
            });

            // Delete data
            if (dataToBeDeleted.Count > 0)
            {
                console.LogProcess("Deleting " + dataToBeDeleted.Count + " items");

                var deletedResult = await PlayFabService.DeleteCurrencyData(targetTitleID, dataToBeDeleted);

                if (deletedResult == null)
                {
                    console.LogError("Deleting currency data failed.");
                    return;
                }
            }

            // - UPDATE

            // Find all items in the source data that don't match target or doesn't exist
            List <VirtualCurrencyData> dataThatNeedsUpdate = sourceData.FindAll((PlayFab.AdminModels.VirtualCurrencyData sourceCurrency) => {
                var needsUpdate = true;
                foreach (VirtualCurrencyData targetCurrency in targetData)
                {
                    if (targetCurrency.CurrencyCode == sourceCurrency.CurrencyCode && targetCurrency.Equals(sourceCurrency))
                    {
                        needsUpdate = false;
                    }
                }
                return(needsUpdate);
            });

            if (dataThatNeedsUpdate.Count == 0)
            {
                console.LogSuccess("Found no data to be updated");
                return;
            }

            // Update data
            if (dataThatNeedsUpdate.Count > 0 || forceOverWrite)
            {
                console.LogProcess("Updating " + dataThatNeedsUpdate.Count + " items");

                var updatedResult = await PlayFabService.UpdateCurrencyData(targetTitleID, dataThatNeedsUpdate);

                if (updatedResult == null)
                {
                    console.LogError("Updating currency data failed.");
                    return;
                }
            }

            console.LogSuccess("Completed Migration of currency data");
        }