using Google.Ads.GoogleAds.Lib; using Google.Ads.GoogleAds.V22.Services; using Google.Ads.GoogleAds.V22.Resources; using Google.Ads.GoogleAds.V22.Enums; using GoogleApi.Configuration; using GoogleApi.Models; using Microsoft.Extensions.Options; // Alias to avoid namespace conflicts using GAdsServices = global::Google.Ads.GoogleAds.Services; namespace GoogleApi.Services; /// /// Service for querying Google Ads audience segments and geo targets. /// public sealed class AudienceService { private readonly GoogleAdsConfig _config; private readonly GoogleAdsClientFactory _clientFactory; private readonly ILogger _logger; public AudienceService( IOptions config, GoogleAdsClientFactory clientFactory, ILogger logger) { _config = config.Value; _clientFactory = clientFactory; _logger = logger; } /// /// Get all available audience segments (affinity, in-market, life events, detailed demographics) /// public async Task GetAudienceSegmentsAsync( GoogleAdsContext context, string requestId, CancellationToken ct) { _logger.LogInformation("[Audience] Fetching audience segments | RequestId={RequestId}", requestId); if (!_clientFactory.IsRealApiEnabled || string.IsNullOrWhiteSpace(context.CustomerId)) { return GetEmulatedAudienceSegments(requestId); } try { var client = _clientFactory.CreateClient(context); var googleAdsService = client.GetService(GAdsServices.V22.GoogleAdsService); var customerId = context.CustomerId; var response = new AudienceSegmentsResponse(); // Query user interests (Affinity + In-Market) var userInterestQuery = @" SELECT user_interest.user_interest_id, user_interest.name, user_interest.taxonomy_type, user_interest.availabilities FROM user_interest WHERE user_interest.taxonomy_type IN ('AFFINITY', 'IN_MARKET')"; var userInterestResults = googleAdsService.Search(customerId, userInterestQuery); foreach (var row in userInterestResults) { var ui = row.UserInterest; var segment = new AudienceSegment { Id = ui.UserInterestId, Name = ui.Name, Type = ui.TaxonomyType.ToString() }; if (ui.TaxonomyType == UserInterestTaxonomyTypeEnum.Types.UserInterestTaxonomyType.Affinity) response.Affinity.Add(segment); else if (ui.TaxonomyType == UserInterestTaxonomyTypeEnum.Types.UserInterestTaxonomyType.InMarket) response.InMarket.Add(segment); } // Query life events var lifeEventQuery = @" SELECT life_event.id, life_event.name FROM life_event"; var lifeEventResults = googleAdsService.Search(customerId, lifeEventQuery); foreach (var row in lifeEventResults) { var le = row.LifeEvent; response.LifeEvents.Add(new AudienceSegment { Id = le.Id, Name = le.Name, Type = "LIFE_EVENT" }); } // Query detailed demographics var detailedDemoQuery = @" SELECT detailed_demographic.id, detailed_demographic.name FROM detailed_demographic"; var detailedDemoResults = googleAdsService.Search(customerId, detailedDemoQuery); foreach (var row in detailedDemoResults) { var dd = row.DetailedDemographic; response.DetailedDemographics.Add(new AudienceSegment { Id = dd.Id, Name = dd.Name, Type = "DETAILED_DEMOGRAPHIC" }); } _logger.LogInformation( "[Audience] Retrieved {Total} segments (Affinity={Affinity}, InMarket={InMarket}, LifeEvents={Life}, Demographics={Demo}) | RequestId={RequestId}", response.TotalCount, response.Affinity.Count, response.InMarket.Count, response.LifeEvents.Count, response.DetailedDemographics.Count, requestId); return ProviderResponse.Success(requestId, response); } catch (Exception ex) { _logger.LogError(ex, "[Audience] Failed to fetch segments | RequestId={RequestId}", requestId); return ProviderResponse.Fail(requestId, "API_ERROR", ex.Message); } } /// /// Search for geo target constants by name /// public async Task SearchGeoTargetsAsync( GeoTargetSearchPayload payload, GoogleAdsContext context, string requestId, CancellationToken ct) { _logger.LogInformation("[Audience] Searching geo targets: {Query} | RequestId={RequestId}", payload.Query, requestId); if (!_clientFactory.IsRealApiEnabled || string.IsNullOrWhiteSpace(context.CustomerId)) { return GetEmulatedGeoTargets(payload.Query, requestId); } try { var client = _clientFactory.CreateClient(context); var geoService = client.GetService(GAdsServices.V22.GeoTargetConstantService); var request = new SuggestGeoTargetConstantsRequest { Locale = "en", CountryCode = payload.CountryCode ?? "US", LocationNames = new SuggestGeoTargetConstantsRequest.Types.LocationNames() }; request.LocationNames.Names.Add(payload.Query); var response = await geoService.SuggestGeoTargetConstantsAsync(request); var results = response.GeoTargetConstantSuggestions .Take(payload.MaxResults) .Select(s => new GeoTarget { Id = s.GeoTargetConstant.Id, Name = s.GeoTargetConstant.Name, CanonicalName = s.GeoTargetConstant.CanonicalName, TargetType = s.GeoTargetConstant.TargetType, CountryCode = s.GeoTargetConstant.CountryCode, ParentGeoTarget = s.GeoTargetConstant.ParentGeoTarget }) .ToList(); _logger.LogInformation("[Audience] Found {Count} geo targets for '{Query}' | RequestId={RequestId}", results.Count, payload.Query, requestId); return ProviderResponse.Success(requestId, new GeoTargetSearchResponse { Query = payload.Query, Results = results }); } catch (Exception ex) { _logger.LogError(ex, "[Audience] Failed to search geo targets | RequestId={RequestId}", requestId); return ProviderResponse.Fail(requestId, "API_ERROR", ex.Message); } } #region Emulated Responses private ProviderResponse GetEmulatedAudienceSegments(string requestId) { _logger.LogInformation("[Audience] Returning emulated audience segments | RequestId={RequestId}", requestId); var response = new AudienceSegmentsResponse { Affinity = new List { new() { Id = 80001, Name = "Sports & Fitness/Sports Fans", Type = "AFFINITY" }, new() { Id = 80002, Name = "Sports & Fitness/Health & Fitness Buffs", Type = "AFFINITY" }, new() { Id = 80003, Name = "Technology/Technophiles", Type = "AFFINITY" }, new() { Id = 80004, Name = "Travel/Travel Buffs", Type = "AFFINITY" }, new() { Id = 80005, Name = "Food & Dining/Foodies", Type = "AFFINITY" }, new() { Id = 80006, Name = "Home & Garden/Home Decor Enthusiasts", Type = "AFFINITY" }, new() { Id = 80007, Name = "Media & Entertainment/Movie Lovers", Type = "AFFINITY" }, new() { Id = 80008, Name = "Media & Entertainment/Music Lovers", Type = "AFFINITY" }, new() { Id = 80009, Name = "Shoppers/Value Shoppers", Type = "AFFINITY" }, new() { Id = 80010, Name = "Shoppers/Luxury Shoppers", Type = "AFFINITY" }, new() { Id = 80011, Name = "Lifestyles & Hobbies/Pet Lovers", Type = "AFFINITY" }, new() { Id = 80012, Name = "Lifestyles & Hobbies/Outdoor Enthusiasts", Type = "AFFINITY" }, new() { Id = 80013, Name = "News & Politics/Avid News Readers", Type = "AFFINITY" }, new() { Id = 80014, Name = "Beauty & Wellness/Beauty Mavens", Type = "AFFINITY" }, new() { Id = 80015, Name = "Vehicles & Transportation/Auto Enthusiasts", Type = "AFFINITY" }, }, InMarket = new List { new() { Id = 90001, Name = "Apparel & Accessories/Athletic Apparel", Type = "IN_MARKET" }, new() { Id = 90002, Name = "Autos & Vehicles/Motor Vehicles (New)", Type = "IN_MARKET" }, new() { Id = 90003, Name = "Autos & Vehicles/Motor Vehicles (Used)", Type = "IN_MARKET" }, new() { Id = 90004, Name = "Business Services/Advertising & Marketing Services", Type = "IN_MARKET" }, new() { Id = 90005, Name = "Consumer Electronics/Computers & Peripherals", Type = "IN_MARKET" }, new() { Id = 90006, Name = "Consumer Electronics/Mobile Phones", Type = "IN_MARKET" }, new() { Id = 90007, Name = "Education/Primary & Secondary Schools (K-12)", Type = "IN_MARKET" }, new() { Id = 90008, Name = "Employment/Jobs", Type = "IN_MARKET" }, new() { Id = 90009, Name = "Financial Services/Insurance/Auto Insurance", Type = "IN_MARKET" }, new() { Id = 90010, Name = "Financial Services/Investment Services", Type = "IN_MARKET" }, new() { Id = 90011, Name = "Home & Garden/Home Improvement", Type = "IN_MARKET" }, new() { Id = 90012, Name = "Real Estate/Residential Properties", Type = "IN_MARKET" }, new() { Id = 90013, Name = "Software/Business Software", Type = "IN_MARKET" }, new() { Id = 90014, Name = "Travel/Hotels & Accommodations", Type = "IN_MARKET" }, new() { Id = 90015, Name = "Travel/Air Travel", Type = "IN_MARKET" }, }, LifeEvents = new List { new() { Id = 70001, Name = "About to graduate college", Type = "LIFE_EVENT" }, new() { Id = 70002, Name = "Getting married soon", Type = "LIFE_EVENT" }, new() { Id = 70003, Name = "Recently married", Type = "LIFE_EVENT" }, new() { Id = 70004, Name = "Moving soon", Type = "LIFE_EVENT" }, new() { Id = 70005, Name = "Recently moved", Type = "LIFE_EVENT" }, new() { Id = 70006, Name = "Purchasing a home soon", Type = "LIFE_EVENT" }, new() { Id = 70007, Name = "Recently purchased a home", Type = "LIFE_EVENT" }, new() { Id = 70008, Name = "Starting a new job", Type = "LIFE_EVENT" }, new() { Id = 70009, Name = "Retiring soon", Type = "LIFE_EVENT" }, new() { Id = 70010, Name = "Recently started a business", Type = "LIFE_EVENT" }, }, DetailedDemographics = new List { new() { Id = 60001, Name = "Parental status/Parents/Parents of infants (0-1 years)", Type = "DETAILED_DEMOGRAPHIC" }, new() { Id = 60002, Name = "Parental status/Parents/Parents of toddlers (1-3 years)", Type = "DETAILED_DEMOGRAPHIC" }, new() { Id = 60003, Name = "Parental status/Parents/Parents of preschoolers (3-5 years)", Type = "DETAILED_DEMOGRAPHIC" }, new() { Id = 60004, Name = "Parental status/Parents/Parents of grade schoolers (6-12 years)", Type = "DETAILED_DEMOGRAPHIC" }, new() { Id = 60005, Name = "Parental status/Parents/Parents of teens (13-17 years)", Type = "DETAILED_DEMOGRAPHIC" }, new() { Id = 60006, Name = "Marital status/Single", Type = "DETAILED_DEMOGRAPHIC" }, new() { Id = 60007, Name = "Marital status/In a relationship", Type = "DETAILED_DEMOGRAPHIC" }, new() { Id = 60008, Name = "Marital status/Married", Type = "DETAILED_DEMOGRAPHIC" }, new() { Id = 60009, Name = "Education/Current college students", Type = "DETAILED_DEMOGRAPHIC" }, new() { Id = 60010, Name = "Education/Bachelor's degree", Type = "DETAILED_DEMOGRAPHIC" }, new() { Id = 60011, Name = "Education/Advanced degree", Type = "DETAILED_DEMOGRAPHIC" }, new() { Id = 60012, Name = "Homeownership/Homeowners", Type = "DETAILED_DEMOGRAPHIC" }, new() { Id = 60013, Name = "Homeownership/Renters", Type = "DETAILED_DEMOGRAPHIC" }, new() { Id = 60014, Name = "Employment/Industry/Technology", Type = "DETAILED_DEMOGRAPHIC" }, new() { Id = 60015, Name = "Employment/Company size/Large employers (10,000+)", Type = "DETAILED_DEMOGRAPHIC" }, }, RetrievedAt = DateTimeOffset.UtcNow }; return ProviderResponse.Success(requestId, response); } private ProviderResponse GetEmulatedGeoTargets(string query, string requestId) { _logger.LogInformation("[Audience] Returning emulated geo targets for '{Query}' | RequestId={RequestId}", query, requestId); var allTargets = new List { new() { Id = 9061285, Name = "Huntington Beach", CanonicalName = "Huntington Beach,California,United States", TargetType = "City", CountryCode = "US" }, new() { Id = 1013962, Name = "Orange County", CanonicalName = "Orange County,California,United States", TargetType = "County", CountryCode = "US" }, new() { Id = 21137, Name = "California", CanonicalName = "California,United States", TargetType = "State", CountryCode = "US" }, new() { Id = 1014221, Name = "Los Angeles", CanonicalName = "Los Angeles,California,United States", TargetType = "City", CountryCode = "US" }, new() { Id = 1014218, Name = "Los Angeles County", CanonicalName = "Los Angeles County,California,United States", TargetType = "County", CountryCode = "US" }, new() { Id = 9031936, Name = "Irvine", CanonicalName = "Irvine,California,United States", TargetType = "City", CountryCode = "US" }, new() { Id = 9031935, Name = "Costa Mesa", CanonicalName = "Costa Mesa,California,United States", TargetType = "City", CountryCode = "US" }, new() { Id = 9031938, Name = "Newport Beach", CanonicalName = "Newport Beach,California,United States", TargetType = "City", CountryCode = "US" }, new() { Id = 9031937, Name = "Santa Ana", CanonicalName = "Santa Ana,California,United States", TargetType = "City", CountryCode = "US" }, new() { Id = 1023191, Name = "New York", CanonicalName = "New York,New York,United States", TargetType = "City", CountryCode = "US" }, new() { Id = 21167, Name = "New York", CanonicalName = "New York,United States", TargetType = "State", CountryCode = "US" }, new() { Id = 1014895, Name = "Chicago", CanonicalName = "Chicago,Illinois,United States", TargetType = "City", CountryCode = "US" }, new() { Id = 2840, Name = "United States", CanonicalName = "United States", TargetType = "Country", CountryCode = "US" }, }; // Simple filter by query var queryLower = query.ToLowerInvariant(); var matches = allTargets .Where(t => t.Name.ToLowerInvariant().Contains(queryLower) || t.CanonicalName.ToLowerInvariant().Contains(queryLower)) .Take(10) .ToList(); return ProviderResponse.Success(requestId, new GeoTargetSearchResponse { Query = query, Results = matches }); } #endregion }