public async Task GetProspectsForTrade()
        {
            var trade = new Trade()
            {
                Id = "-12039",
                Name = "Handyman Services"
            };
            var ha = new HomeAdvisor();
            var prospects = await ha.GetProspectsForTrade(trade, "10707");

            Assert.NotNull(prospects);
            Assert.NotEqual(0, prospects.Count);
        }
        public void GetProspectsForTradesWithZipCodes()
        {
            var tradeHandyman = new Trade()
            {
                Id = "-12039",
                Name = "Handyman Services"
            };

            var tradePlumbing = new Trade()
            {
                Id = "-12058",
                Name = "Plumbing"
            };

            var trades = new List<Trade>();
            trades.Add(tradeHandyman);
            trades.Add(tradePlumbing);

            var zipCodes = new List<string>();
            zipCodes.Add("10707");
            zipCodes.Add("11782");

            var ha = new HomeAdvisor();
            var results = ha.GetProspectsForTradesWithZipCodes(trades, zipCodes);

            using (var file = new System.IO.StreamWriter("prospects.txt"))
            {
                foreach (var result in results)
                {
                    file.WriteLine($"{result.Name} - {result.Service.Name} - {result.PostalCode} - {result.StreetAddress}");
                }
            }
                

            Assert.NotNull(results);
            Assert.NotEqual(0, results.Count);
        }
        public async Task<List<Prospect>> GetProspectsForTrade(Trade trade, string postalCode)
        {
            List<Prospect> prospects = null;

            // URL to POST to, requiring following form data:
            // - findContractor: searchByZip
            // - catOid:         [trade ID here]
            // - zip:            [zip code here]
            var values = new List<KeyValuePair<string, string>>();
            values.Add(new KeyValuePair<string,string>("findContractor", "searchByZip"));
            values.Add(new KeyValuePair<string,string>("catOid", trade.Id));
            values.Add(new KeyValuePair<string,string>("zip", postalCode));

            using (var postContent = new FormUrlEncodedContent(values))
            using (var client = new HttpClient())
            using (var response = await client.PostAsync(_proReviewsUrl, postContent))
            using (var content = response.Content)
            {
                // Now have first page with the review content
                var xhtml = await  content.ReadAsStringAsync();

                // Parse the XHTML result
                var doc = new HtmlDocument();
                doc.LoadHtml(xhtml);
                
                // Look for the review pane containing prospects
                var reviewPane = doc.DocumentNode.SelectSingleNode("//div[@class='xmd-content-main']");

                var prospectDivClass = string.Format(".//div[@data-srzip='{0}']", postalCode);
                foreach (var child in reviewPane.SelectNodes(prospectDivClass))
                {
                    var prospect = new Prospect()
                    {
                        Service = trade,
                        RatingAvailable = false,
                        Id = child.Attributes["data-spid"].Value
                    };

                    // Parse the itemprops
                    foreach (var span in child.SelectNodes(".//span[@itemprop]"))
                    {
                        var attrValue = span.GetAttributeValue("itemprop", "").ToLower();

                        switch (attrValue)
                        {
                            case "name":
                                prospect.Name = span.InnerText;
                                break;
                            case "telephone":
                                prospect.Phone = span.InnerText;
                                break;
                            case "streetaddress":
                                prospect.StreetAddress = span.InnerText;
                                break;
                            case "addresslocality":
                                prospect.AddressLocality = span.InnerText;
                                break;
                            case "addressregion":
                                prospect.AddressRegion = span.InnerText;
                                break;
                            case "postalcode":
                                prospect.PostalCode = span.InnerText;
                                break;
                            default:
                                break;
                        }
                    }

                    // Get the URL for the detail page of the prospect
                    var urlNode = child.SelectNodes(".//a[@itemprop='url']").FirstOrDefault();
                    if (urlNode != null)
                    {
                        prospect.Url = new Uri(UrlCombine(_rootUrl, urlNode.Attributes["href"].Value));
                    }

                    var ratingRefNodes = child.SelectNodes(".//div[@class='l-column ratings-reference']/div");
                    if (ratingRefNodes != null && ratingRefNodes.First().Attributes["class"].Value.Equals("t-stars-small t-stars-rating"))
                    {
                        prospect.RatingAvailable = true;

                        // Parse star percentage
                        var reviewRatingNode = ratingRefNodes[0].ChildNodes.Where(x => x.Name.Equals("div")).FirstOrDefault();
                        if (reviewRatingNode != null)
                        {
                            // TODO: Move cleaning double to utility class
                            // Replace non digit and non decimal characters with nothing
                            var ratingCleaned = Regex.Replace(reviewRatingNode.Attributes["style"].Value, "[^0-9.]", "");
                            var starRating = 0.0;
                            if (double.TryParse(ratingCleaned, out starRating))
                            {
                                prospect.StarRating = starRating;
                            }
                        }

                        // Parse review count
                        var reviewCountNode = ratingRefNodes.Where(x => x.Attributes["class"].Value.Contains("verified-reviews")).FirstOrDefault();
                        if (reviewCountNode != null)
                        {
                            // TODO: Move cleaning integer to utility class
                            // Replace non digit characters with nothing
                            var reviewCountCleaned = Regex.Replace(reviewCountNode.InnerText, "[^0-9]", "");
                            var reviewCount = 0;
                            if (int.TryParse(reviewCountCleaned, out reviewCount))
                            {
                                prospect.ReviewCount = reviewCount;
                            }
                        }
                    }

                    // Finished parsing itemprops, add the object to the list
                    if (prospects == null) prospects = new List<Prospect>();
                    prospects.Add(prospect);
                }
            }

            return prospects;
        }