public string RecommendPackageUpgrade(PackageSelectionViewData packageSelectionViewData,
                                              List <Package> packages)
        {
            // This is the delta amount we will consider for extra payment from the current selected price.
            const decimal packageUpgradeDelta = 10m;

            // Find out if there is a package which contains all the selected tests,
            // and has few more tests available,
            // and its price is less than or equal to the (current selected tests price + delta amount)
            if (packages.Any(p => (p.Price <= packageSelectionViewData.OfferPrice + packageUpgradeDelta) &&
                             (p.Id != packageSelectionViewData.SelectedPackageId) &&
                             (packageSelectionViewData.SelectedPackageId <= 0 || p.Price > packageSelectionViewData.OfferPrice) &&
                             (packageSelectionViewData.SelectedTestIds.All(st => p.Tests.Select(t => t.Id).Contains(st)))))
            {
                // Get the packages which meet our criteria.
                var upgradePackageOptions =
                    packages.Where(p => (p.Price <= packageSelectionViewData.OfferPrice + packageUpgradeDelta) &&
                                   (p.Id != packageSelectionViewData.SelectedPackageId) &&
                                   (packageSelectionViewData.SelectedPackageId <= 0 ||
                                    p.Price > packageSelectionViewData.OfferPrice) &&
                                   (packageSelectionViewData.SelectedTestIds.All(
                                        st => p.Tests.Select(t => t.Id).Contains(st)))).ToList();

                // If there are more than packages in our new list and there is one package which falls
                // in our upsell category and its price is more than the current selection then we will
                // take this out.
                if (!upgradePackageOptions.IsNullOrEmpty() && upgradePackageOptions.Any(p => p.Price <= packageSelectionViewData.OfferPrice + packageUpgradeDelta && p.Price > packageSelectionViewData.OfferPrice))
                {
                    upgradePackageOptions.RemoveAll(p => p.Price <= packageSelectionViewData.OfferPrice);
                }

                // This will be the package to which we will recommend to upgrade.
                Package upgradePackage = null;

                // This will be the price to which we can recommend to upgrade.
                var minimumUpgradePrice = packageSelectionViewData.OfferPrice + packageUpgradeDelta;


                // We will take out the package whcih falls into the set criteria and has minimum price with in that limit.
                foreach (var upgradePackageOption in upgradePackageOptions)
                {
                    if (minimumUpgradePrice >= upgradePackageOption.Price)
                    {
                        upgradePackage      = upgradePackageOption;
                        minimumUpgradePrice = upgradePackageOption.Price;
                    }
                }

                // Get the tests included in the package which is to be recommended with the tests which are not currently selected.
                // And the additional cost the user has to pay.
                if (upgradePackage != null)
                {
                    var upgradePackageTestIds = upgradePackage.Tests.Select(t => t.Id);
                    var testIdsToBeAdded      =
                        upgradePackageTestIds.Where(t => !packageSelectionViewData.SelectedTestIds.Contains(t));
                    var testNamesToBeAdded =
                        packageSelectionViewData.TestViewData.Where(t => testIdsToBeAdded.Contains(t.TestId)).Select(
                            t => t.TestName).ToArray();

                    if (upgradePackage.Price > packageSelectionViewData.OfferPrice)
                    {
                        return(string.Format(
                                   "With additional ${0}, you can buy {1} (Discounted) and you can get screened for {2} also!",
                                   Math.Round(minimumUpgradePrice - packageSelectionViewData.OfferPrice, 2),
                                   upgradePackage.Name, string.Join(",", testNamesToBeAdded)));
                    }
                    if (upgradePackage.Price < packageSelectionViewData.OfferPrice)
                    {
                        return(string.Format(
                                   "Your current order costs ${0}. We recommend you buy {1} as it will cost ${2} less. Further it will include your current selection of tests and {3}.",
                                   Math.Round(packageSelectionViewData.OfferPrice, 2),
                                   upgradePackage.Name,
                                   Math.Round(packageSelectionViewData.OfferPrice - minimumUpgradePrice, 2),
                                   string.Join(",", testNamesToBeAdded)));
                    }
                    if (upgradePackage.Price == packageSelectionViewData.OfferPrice)
                    {
                        return(string.Format(
                                   "With same price i.e. ${0}, you can buy {1} (Discounted) and can get screened for {2} also!",
                                   Math.Round(packageSelectionViewData.OfferPrice, 2), upgradePackage.Name,
                                   string.Join(",", testNamesToBeAdded)));
                    }
                }
            }

            return(string.Empty);
        }
        public PackageSelectionViewData Create(long selectedPackageId, List <long> packageTestIds,
                                               List <long> independentTestIds, List <Package> packages, List <Test> tests)
        {
            var selectedPackage = selectedPackageId > 0
                                      ? packages.Single(p => p.Id == selectedPackageId)
                                      : null;

            var selectedPackageTestIds = selectedPackage != null
                                             ? selectedPackage.Tests.Select(t => t.Id)
                                             : new List <long>();

            if (selectedPackage != null)
            {
                packageTestIds = selectedPackage.Tests.Select(t => t.Id).ToList();
            }

            if (selectedPackage == null && !packageTestIds.IsNullOrEmpty())
            {
                independentTestIds = independentTestIds.IsNullOrEmpty() ? new List <long>() : independentTestIds;
                independentTestIds.AddRange(packageTestIds);
                packageTestIds.Clear();
            }

            var packageName        = selectedPackage == null ? string.Empty : selectedPackage.Name;
            var packageDescription = selectedPackage == null ? string.Empty : selectedPackage.Description;

            var selectedTestIds = new List <long>();

            if (!packageTestIds.IsNullOrEmpty())
            {
                selectedTestIds.AddRange(packageTestIds);
            }
            if (!independentTestIds.IsNullOrEmpty())
            {
                selectedTestIds.AddRange(independentTestIds);
            }

            List <string> selectedTestNames = null;

            if (!selectedTestIds.IsNullOrEmpty())
            {
                if (selectedPackage != null)
                {
                    independentTestIds = selectedTestIds.Where(st => !selectedPackageTestIds.Contains(st)).ToList();
                }

                if (!independentTestIds.IsNullOrEmpty())
                {
                    selectedTestNames = tests.Where(t => independentTestIds.Contains(t.Id)).Select(t => t.Name).ToList();
                }
            }

            var packageViewData = _packageViewDataFactory.Create(packages);
            var testViewData    = _testViewDataFactory.Create(selectedPackage, selectedTestIds, tests);

            var packageSelectionViewData = new PackageSelectionViewData
            {
                PackageViewData            = packageViewData,
                TestViewData               = testViewData,
                SelectedPackageDescription = packageDescription,
                SelectedPackageId          = selectedPackageId,
                SelectedPackageName        = packageName,
                SelectedTestIds            = selectedTestIds,
                SelectedPackageTestIds     = packageTestIds,
                IndependentTestNames       = selectedTestNames,
                IndependentTestIds         = independentTestIds,
                OfferPrice = Math.Round(testViewData.Sum(tv => tv.OfferPrice), 2)
            };

            if (packageSelectionViewData.SelectedPackageId > 0 && selectedPackage != null)
            {
                var packagePrice = selectedPackage.Price;

                var selectedTestsNotInPackage =
                    tests.Where(t => selectedTestIds.Contains(t.Id) && !selectedPackageTestIds.Contains(t.Id)).ToList();
                var testsPrice = selectedTestsNotInPackage.Sum(t => t.Price);

                packageSelectionViewData.OfferPrice = packagePrice + testsPrice;
            }

            return(packageSelectionViewData);
        }