HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/ai.naniguide.com/database/seeders/Demo/Stage22_AdsSeeder.php
<?php

namespace Database\Seeders\Demo;

use App\Model\AdAbTest;
use App\Model\AdAbVariant;
use App\Model\AdAccount;
use App\Model\AdActivityLog;
use App\Model\AdAudience;
use App\Model\AdAutomationRule;
use App\Model\AdCampaign;
use App\Model\AdCampaignPlatform;
use App\Model\AdConversion;
use App\Model\AdCreative;
use App\Model\AdLeadFormConfig;
use App\Model\AdLead;
use App\Model\AdMetric;
use App\Model\AdPixel;
use App\Model\AdPlatform;
use App\Model\AdProduct;
use App\Model\AdProductCatalog;
use App\Model\AdPublishLog;
use App\Model\AdTemplate;
use App\Model\Customer;
use App\Model\Plan;
use App\Model\Setting;
use Illuminate\Support\Facades\DB;

/**
 * Stage 22 — Ads module demo data.
 *
 * Seeds realistic data for ALL 13 ads phases (A–M):
 *   - Enables ads_enabled='yes' on all plans (so the sidebar nav appears)
 *   - Per customer: 4 ad_platforms (one per platform), 1-2 ad_accounts each
 *   - 3-5 ad_campaigns per customer with mixed statuses (draft/active/paused)
 *   - 2-3 ad_audiences from email lists
 *   - 1-2 ad_ab_tests per customer (with 2-3 variants each)
 *   - 1-2 ad_automation_rules per customer
 *   - 1-2 ad_pixels per customer
 *   - 1 ad_product_catalog with 5-10 products per customer
 *   - 1 ad_lead_form_config per customer with 3-5 leads
 *   - 14 days of ad_metrics for active campaigns
 *   - System ad_templates (6 starter templates, available to all customers)
 *   - Activity logs for all major actions
 *
 * **Critical:** This stage runs AFTER Stage20_ManifestWriter so all customers
 * exist with subscriptions. Seeders skip silently if model classes don't exist.
 */
class Stage22_AdsSeeder extends AbstractDemoSeeder
{
    protected int $fakerOffset = 22;

    public function run(): void
    {
        if (!class_exists(AdPlatform::class)) {
            $this->log('AdPlatform model missing — skipping');
            return;
        }

        $f = $this->faker();

        // 0. Enable ads on all plans — set MAX_ADS_* quotas to unlimited (null).
        $this->time('enabling ads on all plans', function () {
            $service = app(\App\Services\Plans\PlansService::class);
            $plans = Plan::all();
            foreach ($plans as $plan) {
                $service->setQuotas($plan, array_merge(
                    // Preserve existing quotas — read current rows and merge
                    \App\Model\PlanQuota::where('plan_id', $plan->id)->pluck('limit_value', 'quota_key')->all(),
                    [
                        \App\Services\Plans\Quotas\QuotaKey::MAX_ADS_PLATFORMS->value => null,
                        \App\Services\Plans\Quotas\QuotaKey::MAX_ADS_CAMPAIGNS->value => null,
                        \App\Services\Plans\Quotas\QuotaKey::MAX_ADS_AUDIENCES->value => null,
                    ]
                ));
            }
            $this->log('enabled ads (unlimited) on ' . $plans->count() . ' plans');
        });

        // 0.5 Phase N — Admin self-service credentials (seed settings so admin
        // wizard shows pre-configured state instead of empty form). Mock mode is
        // enabled so testAppCredentials() returns happy path for all 4 platforms.
        $this->time('seeding Phase N admin credentials', function () {
            if (!class_exists(Setting::class)) {
                $this->log('Setting model missing — skipping Phase N');
                return;
            }
            $creds = [
                'ads.mock_mode' => 'yes',
                'ads.meta.app_id' => '3847291085647203',
                'ads.meta.app_secret' => 'demo_meta_secret_abcdef0123456789',
                'ads.google.client_id' => '847291085647-abc123def456.apps.googleusercontent.com',
                'ads.google.client_secret' => 'GOCSPX-demo_google_client_secret_xyz',
                'ads.google.developer_token' => 'DemoDevTokenXXXXXXXXXX',
                'ads.tiktok.app_id' => '7291085647203',
                'ads.tiktok.app_secret' => 'demo_tiktok_secret_abcdef0123456789',
                'ads.linkedin.client_id' => '78abc123def456ghi',
                'ads.linkedin.client_secret' => 'demo_linkedin_secret_xyz123',
            ];
            foreach ($creds as $key => $value) {
                try {
                    Setting::set($key, $value);
                } catch (\Throwable $e) {
                    // ignore if setting table schema differs
                }
            }
            $this->log('seeded ' . count($creds) . ' Phase N admin credentials');
        });

        // 1. System templates — read sample JSON + SVG thumbnails FROM DISK.
        //    These files live in resources/themes/default/master/sample/ads/
        //    and are synced one-way from the builderjs repo via copy.sh.
        //    NEVER inline content arrays here — see docs/builderjs-integration/BUILDER_CONNECT.md
        //    "Sample Templates + Thumbnails — ONE-WAY RULE".
        $this->time('seeding system ad templates from disk', function () {
            $manifest = [
                ['name' => 'Blank',               'platform' => AdTemplate::PLATFORM_UNIVERSAL, 'format' => AdTemplate::FORMAT_IMAGE,    'featured' => false, 'file' => 'Blank'],
                ['name' => 'Facebook Feed',       'platform' => 'meta',                          'format' => AdTemplate::FORMAT_FEED,     'featured' => true,  'file' => 'FacebookFeed'],
                ['name' => 'Instagram Feed',      'platform' => 'meta',                          'format' => AdTemplate::FORMAT_IMAGE,    'featured' => true,  'file' => 'InstagramFeed'],
                ['name' => 'Instagram Story',     'platform' => 'meta',                          'format' => AdTemplate::FORMAT_STORY,    'featured' => true,  'file' => 'InstagramStory'],
                ['name' => 'Meta Carousel',       'platform' => 'meta',                          'format' => AdTemplate::FORMAT_CAROUSEL, 'featured' => false, 'file' => 'MetaCarousel'],
                ['name' => 'Google Responsive',   'platform' => 'google',                        'format' => AdTemplate::FORMAT_IMAGE,    'featured' => true,  'file' => 'GoogleResponsive'],
                ['name' => 'TikTok Video',        'platform' => 'tiktok',                        'format' => AdTemplate::FORMAT_VIDEO,    'featured' => false, 'file' => 'TikTokVideo'],
                ['name' => 'LinkedIn Sponsored',  'platform' => 'linkedin',                      'format' => AdTemplate::FORMAT_FEED,     'featured' => false, 'file' => 'LinkedInSponsored'],
            ];
            $base = resource_path('themes/default/master/sample/ads');

            // Idempotent: wipe system templates and re-seed from the manifest so
            // changes to the sample JSONs propagate cleanly on re-run.
            AdTemplate::whereNull('customer_id')->delete();

            $seeded = 0;
            foreach ($manifest as $meta) {
                $jsonPath = $base . '/' . $meta['file'] . '.json';
                $svgPath  = $base . '/' . $meta['file'] . '.svg';
                if (!is_file($jsonPath)) {
                    $this->log('missing sample file ' . $jsonPath . ' — skipping');
                    continue;
                }
                $content = json_decode((string) file_get_contents($jsonPath), true);
                if (!is_array($content)) {
                    $this->log('invalid JSON in ' . $jsonPath . ' — skipping');
                    continue;
                }
                $tpl = AdTemplate::create([
                    'uid' => \Illuminate\Support\Str::random(32),
                    'customer_id' => null,
                    'name' => $meta['name'],
                    'platform' => $meta['platform'],
                    'format' => $meta['format'],
                    'category' => null,
                    'is_featured' => $meta['featured'],
                    'use_count' => 0,
                    'content' => $content,
                ]);
                if (is_file($svgPath)) {
                    $tpl->updateThumbnailFromPath($svgPath);
                }
                $seeded++;
            }
            $this->log('created ' . $seeded . ' system ad templates from disk');
        });

        // Get demo customers — focus on top customers for richest data
        $customers = Customer::limit(8)->get();
        if ($customers->isEmpty()) {
            $this->log('no customers found — skipping per-customer ads data');
            return;
        }

        $platformsCreated = 0;
        $campaignsCreated = 0;
        $audiencesCreated = 0;
        $abTestsCreated = 0;
        $automationsCreated = 0;
        $pixelsCreated = 0;
        $catalogsCreated = 0;
        $leadFormsCreated = 0;
        $metricsCreated = 0;

        // Collect rows for bulk insert across all customers — these tables don't
        // need IDs returned to drive subsequent inserts (leaf tables, no children),
        // so a single bulk insert per table is dramatically faster than per-row
        // Eloquent create(). Tables touched: ad_metrics, ad_conversions, ad_products,
        // ad_leads, ad_publish_logs, ad_activity_logs.
        $now = now();
        $metricRows = [];
        $conversionRows = [];
        $productRows = [];
        $leadRows = [];
        $publishLogRows = [];
        $activityLogRows = [];

        $this->time('seeding per-customer ads data', function () use (
            $customers, $f, $now,
            &$platformsCreated, &$campaignsCreated, &$audiencesCreated, &$abTestsCreated,
            &$automationsCreated, &$pixelsCreated, &$catalogsCreated, &$leadFormsCreated, &$metricsCreated,
            &$metricRows, &$conversionRows, &$productRows, &$leadRows, &$publishLogRows, &$activityLogRows
        ) {
            foreach ($customers as $customer) {
                // 2. Platforms — one per platform type
                $platforms = [];
                foreach (AdPlatform::PLATFORMS as $platformType) {
                    if (AdPlatform::where('customer_id', $customer->id)->where('platform', $platformType)->exists()) {
                        $platforms[$platformType] = AdPlatform::where('customer_id', $customer->id)->where('platform', $platformType)->first();
                        continue;
                    }
                    $p = AdPlatform::create([
                        'uid' => uniqid(),
                        'customer_id' => $customer->id,
                        'platform' => $platformType,
                        'name' => AdPlatform::PLATFORM_LABELS[$platformType],
                        'access_token' => 'demo_access_' . $platformType . '_' . $customer->id,
                        'token_expires_at' => now()->addDays(60),
                        'platform_user_id' => (string) ($customer->id * 1000 + array_search($platformType, AdPlatform::PLATFORMS)),
                        'platform_user_name' => 'Customer ' . $customer->id,
                        'scopes' => ['ads_management', 'ads_read'],
                        'status' => AdPlatform::STATUS_ACTIVE,
                        'health_status' => AdPlatform::HEALTH_HEALTHY,
                        'last_health_check_at' => now()->subMinutes(mt_rand(5, 60)),
                    ]);
                    $platforms[$platformType] = $p;
                    $platformsCreated++;

                    // Ad accounts — 1-2 per platform
                    for ($i = 0; $i < mt_rand(1, 2); $i++) {
                        AdAccount::create([
                            'uid' => uniqid(),
                            'ad_platform_id' => $p->id,
                            'remote_account_id' => 'act_' . $platformType . '_' . $customer->id . '_' . $i,
                            'name' => 'Account ' . ($i + 1) . ' for Customer ' . $customer->id,
                            'currency' => 'USD',
                            'timezone' => 'America/New_York',
                            'status' => 'active',
                            'is_default' => $i === 0,
                        ]);
                    }
                }

                // 3. Campaigns — 3-5 with mixed statuses
                $campaigns = [];
                $campaignSpecs = [
                    ['Summer Sale Promo', 'sales', 'active'],
                    ['Brand Awareness Q1', 'awareness', 'active'],
                    ['Newsletter Growth', 'leads', 'paused'],
                    ['Holiday Campaign Draft', 'sales', 'draft'],
                    ['Webinar Promotion', 'traffic', 'completed'],
                ];
                foreach (array_slice($campaignSpecs, 0, mt_rand(3, 5)) as $spec) {
                    $campaign = AdCampaign::create([
                        'uid' => uniqid(),
                        'customer_id' => $customer->id,
                        'name' => $spec[0],
                        'objective' => $spec[1],
                        'status' => $spec[2],
                        'daily_budget' => mt_rand(20, 500),
                        'budget_currency' => 'USD',
                        'bid_strategy' => 'auto',
                        'utm_source' => 'acelle_ads',
                        'utm_medium' => 'paid_social',
                        'utm_campaign' => strtolower(str_replace(' ', '_', $spec[0])),
                        'schedule_start' => now()->subDays(mt_rand(7, 30)),
                        'published_at' => $spec[2] !== 'draft' ? now()->subDays(mt_rand(5, 25)) : null,
                    ]);
                    $campaigns[] = $campaign;
                    $campaignsCreated++;

                    // Link to 1-2 platforms
                    $linkedPlatforms = array_rand($platforms, min(2, count($platforms)));
                    if (!is_array($linkedPlatforms)) $linkedPlatforms = [$linkedPlatforms];
                    foreach ($linkedPlatforms as $pt) {
                        AdCampaignPlatform::create([
                            'ad_campaign_id' => $campaign->id,
                            'ad_platform_id' => $platforms[$pt]->id,
                            'remote_campaign_id' => 'demo_' . $pt . '_' . $campaign->id,
                            'status' => $spec[2] === 'active' ? 'active' : 'pending',
                            'last_synced_at' => now()->subHours(mt_rand(1, 12)),
                        ]);
                    }

                    // 4. Metrics for active/completed campaigns — 14 days with
                    // realistic trends: CTR improves over time (optimization),
                    // weekend dips, budget spent grows accordingly.
                    if (in_array($spec[2], ['active', 'completed'])) {
                        $linkedPlatformKey = is_array($linkedPlatforms) ? $linkedPlatforms[0] : $linkedPlatforms;
                        $platformObj = $platforms[$linkedPlatformKey];
                        for ($d = 0; $d < 14; $d++) {
                            $daysAgo = 13 - $d;
                            $date = now()->subDays($daysAgo);
                            $isWeekend = $date->dayOfWeek === 0 || $date->dayOfWeek === 6;
                            // CTR improves from ~1% → ~3% as campaign optimizes
                            $baseCtrBps = 100 + ($d * 15) + mt_rand(-10, 20); // basis points
                            $impressions = ($isWeekend ? mt_rand(500, 2500) : mt_rand(2000, 6000))
                                * max(1, (int) round(($campaign->daily_budget ?? 50) / 50));
                            $clicks = (int) round($impressions * $baseCtrBps / 10000);
                            $spend = round(min($campaign->daily_budget ?? 50, mt_rand(800, 5500) / 100), 2);
                            $conversions = (int) round($clicks * (mt_rand(20, 80) / 1000));
                            $metricRows[] = [
                                'ad_campaign_id' => $campaign->id,
                                'platform' => $platformObj->platform,
                                'date' => $date->format('Y-m-d'),
                                'impressions' => $impressions,
                                'clicks' => $clicks,
                                'spend' => $spend,
                                'conversions' => $conversions,
                                'ctr' => $impressions ? round(($clicks / $impressions) * 100, 4) : 0,
                                'cpc' => $clicks ? round($spend / $clicks, 2) : 0,
                                'cpm' => round($spend / ($impressions / 1000), 2),
                                'roas' => $spend > 0 ? round(($conversions * 65) / $spend, 2) : 0,
                                'created_at' => $now,
                                'updated_at' => $now,
                            ];
                            $metricsCreated++;
                        }
                    }

                    // 4b. Creative for each campaign — seeded with real BuilderJS
                    // content from disk sample JSONs (same one-way rule as templates)
                    // + an unsplash hero image so the grid-view thumbnail renders.
                    $ctaMap = [
                        'sales' => 'shop_now',
                        'awareness' => 'learn_more',
                        'leads' => 'sign_up',
                        'traffic' => 'learn_more',
                        'engagement' => 'learn_more',
                        'app_installs' => 'download',
                    ];
                    $headlineMap = [
                        'Summer Sale Promo' => 'Up to 40% Off — Ends Sunday',
                        'Brand Awareness Q1' => 'Meet the Team Behind Acelle',
                        'Newsletter Growth' => 'Join 50,000+ Marketers on Our Weekly Digest',
                        'Holiday Campaign Draft' => 'Your Holiday Gift Guide is Here',
                        'Webinar Promotion' => 'Free Masterclass: Email Marketing in 2026',
                    ];
                    $sampleMap = [
                        'sales' => 'FacebookFeed',
                        'awareness' => 'InstagramFeed',
                        'leads' => 'GoogleResponsive',
                        'traffic' => 'FacebookFeed',
                        'engagement' => 'InstagramStory',
                    ];
                    $heroImageMap = [
                        'Summer Sale Promo' => 'https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?w=1080&h=1080&fit=crop',
                        'Brand Awareness Q1' => 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=1080&h=1080&fit=crop',
                        'Newsletter Growth' => 'https://images.unsplash.com/photo-1607082348824-0a96f2a4b9da?w=1200&h=628&fit=crop',
                        'Holiday Campaign Draft' => 'https://images.unsplash.com/photo-1513885535751-8b9238bd345a?w=1080&h=1080&fit=crop',
                        'Webinar Promotion' => 'https://images.unsplash.com/photo-1557804506-669a67965ba0?w=1080&h=1080&fit=crop',
                    ];

                    $sampleBase = resource_path('themes/default/master/sample/ads');
                    $sampleKey = $sampleMap[$spec[1]] ?? 'FacebookFeed';
                    $samplePath = $sampleBase . '/' . $sampleKey . '.json';
                    $builderContent = null;
                    if (is_file($samplePath)) {
                        $decoded = json_decode((string) file_get_contents($samplePath), true);
                        if (is_array($decoded)) $builderContent = $decoded;
                    }

                    $creative = AdCreative::create([
                        'uid' => uniqid(),
                        'customer_id' => $customer->id,
                        'ad_campaign_id' => $campaign->id,
                        'name' => 'Primary creative — ' . $spec[0],
                        'type' => 'image',
                        'headline' => $headlineMap[$spec[0]] ?? ('Discover ' . $spec[0]),
                        'primary_text' => 'Limited time offer — ' . $spec[0] . '. Click to learn more.',
                        'description' => 'Acelle Mail ' . $spec[1] . ' campaign',
                        'call_to_action' => $ctaMap[$spec[1]] ?? 'learn_more',
                        'destination_url' => 'https://example.com/offer/' . strtolower(str_replace(' ', '-', $spec[0])),
                        'image_url' => $heroImageMap[$spec[0]] ?? null,
                        'builder_content' => $builderContent,
                    ]);
                    $campaign->ad_creative_id = $creative->id;
                    $campaign->save();

                    // 4c. Publish logs (1 publish per platform link, mostly success + occasional error)
                    foreach ($linkedPlatforms as $pt) {
                        $isError = mt_rand(1, 100) <= 15; // 15% error rate
                        $publishLogRows[] = [
                            'ad_campaign_id' => $campaign->id,
                            'ad_platform_id' => $platforms[$pt]->id,
                            'platform' => $pt,
                            'action' => 'publish',
                            'status' => $isError ? 'error' : 'success',
                            'request_data' => json_encode(['campaign_name' => $spec[0], 'daily_budget' => $campaign->daily_budget]),
                            'response_data' => $isError ? null : json_encode(['remote_id' => 'demo_' . $pt . '_' . $campaign->id, 'status' => 'active']),
                            'error_message' => $isError ? 'Creative rejected: logo too small (must be ≥200px)' : null,
                            'created_at' => $campaign->published_at ?? now()->subDays(mt_rand(1, 10)),
                            'updated_at' => $now,
                        ];
                    }
                }

                // 5. Audiences — custom (from mail lists) + 1 lookalike + 1 saved/retargeting
                $mailLists = $customer->mailLists ?? collect();
                if (method_exists($customer, 'mailLists')) {
                    $mailLists = $customer->mailLists()->limit(3)->get();
                }
                $firstCustomAudience = null;
                foreach ($mailLists->take(2) as $idx => $list) {
                    $audience = AdAudience::create([
                        'uid' => uniqid(),
                        'customer_id' => $customer->id,
                        'name' => $list->name . ' — Subscribers',
                        'type' => 'custom',
                        'source_type' => 'mail_list',
                        'source_id' => $list->id,
                        'platform' => $idx === 0 ? 'meta' : 'google',
                        'platform_audience_id' => 'aud_custom_' . $list->id,
                        'size_estimate' => mt_rand(500, 50000),
                        'status' => 'synced',
                        'sync_status' => 'completed',
                        'last_synced_at' => now()->subHours(mt_rand(1, 24)),
                    ]);
                    if ($firstCustomAudience === null) $firstCustomAudience = $audience;
                    $audiencesCreated++;
                }

                // Lookalike audience (based on first custom audience)
                if ($firstCustomAudience) {
                    AdAudience::create([
                        'uid' => uniqid(),
                        'customer_id' => $customer->id,
                        'name' => 'Lookalike 1% — ' . $firstCustomAudience->name,
                        'type' => 'lookalike',
                        'source_type' => null,
                        'source_id' => $firstCustomAudience->id,
                        'platform' => 'meta',
                        'platform_audience_id' => 'aud_lal_' . $firstCustomAudience->id,
                        'size_estimate' => mt_rand(80000, 250000),
                        'status' => 'synced',
                        'sync_status' => 'completed',
                        'last_synced_at' => now()->subHours(mt_rand(1, 48)),
                        'settings' => ['similarity_percent' => 1, 'country' => 'US'],
                    ]);
                    $audiencesCreated++;
                }

                // Saved / retargeting audience (website visitors, past purchasers)
                AdAudience::create([
                    'uid' => uniqid(),
                    'customer_id' => $customer->id,
                    'name' => 'Website Visitors (Last 30 Days)',
                    'type' => 'saved',
                    'source_type' => null,
                    'platform' => 'meta',
                    'platform_audience_id' => 'aud_retarget_' . $customer->id,
                    'size_estimate' => mt_rand(5000, 45000),
                    'status' => 'synced',
                    'sync_status' => 'completed',
                    'last_synced_at' => now()->subHours(mt_rand(1, 6)),
                ]);
                $audiencesCreated++;

                // 6. A/B tests — 1-2 per customer if campaigns exist
                if (count($campaigns) > 0) {
                    foreach (array_slice($campaigns, 0, mt_rand(1, 2)) as $campaign) {
                        $abTest = AdAbTest::create([
                            'uid' => uniqid(),
                            'ad_campaign_id' => $campaign->id,
                            'status' => mt_rand(0, 1) ? 'running' : 'completed',
                            'confidence' => mt_rand(80, 99),
                            'settings' => ['significance_threshold' => 95, 'split' => [50, 50]],
                            'started_at' => now()->subDays(mt_rand(3, 14)),
                            'completed_at' => mt_rand(0, 1) ? now()->subDays(mt_rand(1, 5)) : null,
                        ]);
                        $abTestsCreated++;

                        // 2-3 variants with a VISIBLE winner — winner gets 2x CTR
                        $variantCount = mt_rand(2, 3);
                        $winnerIdx = 0; // variant A wins deterministically
                        for ($v = 0; $v < $variantCount; $v++) {
                            $isWinner = $v === $winnerIdx;
                            $impressions = mt_rand(3000, 8000);
                            // Winner gets 3-4% CTR, losers get 1-2% CTR
                            $ctrBps = $isWinner ? mt_rand(300, 420) : mt_rand(100, 200);
                            $clicks = (int) round($impressions * $ctrBps / 10000);
                            $spend = round($impressions / 1000 * mt_rand(300, 800) / 100, 2);
                            AdAbVariant::create([
                                'uid' => uniqid(),
                                'ad_ab_test_id' => $abTest->id,
                                'name' => 'Variant ' . chr(65 + $v) . ($isWinner ? ' — Short urgent headline' : ' — Long descriptive headline'),
                                'weight' => (int) (100 / $variantCount),
                                'impressions' => $impressions,
                                'clicks' => $clicks,
                                'spend' => $spend,
                                'conversions' => (int) round($clicks * ($isWinner ? 0.08 : 0.04)),
                                'ctr' => round(($clicks / $impressions) * 100, 4),
                                'is_winner' => $isWinner,
                            ]);
                        }
                    }
                }

                // 7. Automation rules
                $automationSpecs = [
                    ['Open → Add to Audience', 'email_open', 'add_to_audience'],
                    ['Click → Start Campaign', 'email_click', 'start_campaign'],
                ];
                foreach (array_slice($automationSpecs, 0, mt_rand(1, 2)) as $spec) {
                    AdAutomationRule::create([
                        'uid' => uniqid(),
                        'customer_id' => $customer->id,
                        'name' => $spec[0],
                        'trigger_event' => $spec[1],
                        'action_type' => $spec[2],
                        'status' => 'active',
                        'executions_count' => mt_rand(10, 500),
                        'last_triggered_at' => now()->subHours(mt_rand(1, 48)),
                    ]);
                    $automationsCreated++;
                }

                // 8. Pixels + sample conversions
                $createdPixels = [];
                foreach (['meta', 'google'] as $pp) {
                    $pixel = AdPixel::create([
                        'uid' => uniqid(),
                        'customer_id' => $customer->id,
                        'platform' => $pp,
                        'pixel_id' => $pp . '_pix_' . str_pad((string) $customer->id, 6, '0', STR_PAD_LEFT),
                        'status' => 'active',
                        'last_verified_at' => now()->subHours(mt_rand(1, 24)),
                    ]);
                    $createdPixels[] = $pixel;
                    $pixelsCreated++;
                }

                // 8b. Sample conversions per pixel (showcase pixel tracking)
                $eventNames = ['PageView', 'AddToCart', 'InitiateCheckout', 'Purchase', 'Lead'];
                foreach ($createdPixels as $pixel) {
                    $conversionCount = mt_rand(8, 25);
                    for ($c = 0; $c < $conversionCount; $c++) {
                        $event = $eventNames[array_rand($eventNames)];
                        $value = in_array($event, ['Purchase', 'AddToCart', 'InitiateCheckout'])
                            ? round(mt_rand(1500, 25000) / 100, 2)
                            : null;
                        $conversionRows[] = [
                            'ad_pixel_id' => $pixel->id,
                            'event_name' => $event,
                            'source_url' => 'https://example.com/' . strtolower($event) . '/product-' . mt_rand(1, 100),
                            'value' => $value,
                            'currency' => $value ? 'USD' : null,
                            'attribution' => json_encode(['source' => 'paid_social', 'platform' => $pixel->platform]),
                            'created_at' => now()->subHours(mt_rand(1, 168)),
                            'updated_at' => $now,
                        ];
                    }
                }

                // 9. Product catalog (1 catalog with 5-10 products)
                $catalog = AdProductCatalog::create([
                    'uid' => uniqid(),
                    'customer_id' => $customer->id,
                    'name' => 'Catalog #' . $customer->id,
                    'source_type' => 'manual',
                    'sync_status' => 'completed',
                    'last_synced_at' => now()->subDays(mt_rand(1, 7)),
                    'product_count' => 0,
                ]);
                $catalogsCreated++;

                $productCount = mt_rand(5, 10);
                $sampleProducts = [
                    ['Premium Widget Pro', 'widgets', 49.99],
                    ['Deluxe Gadget Plus', 'gadgets', 99.99],
                    ['Standard Tool Kit', 'tools', 29.99],
                    ['Eco Bundle', 'eco', 79.99],
                    ['Fast Charger', 'electronics', 19.99],
                    ['Smart Lamp', 'electronics', 39.99],
                    ['Coffee Subscription', 'food', 24.99],
                    ['Yoga Mat Pro', 'fitness', 49.99],
                    ['Travel Backpack', 'travel', 89.99],
                    ['Wireless Headphones', 'electronics', 129.99],
                ];
                for ($p = 0; $p < $productCount; $p++) {
                    $sample = $sampleProducts[$p % count($sampleProducts)];
                    $productRows[] = [
                        'ad_product_catalog_id' => $catalog->id,
                        'external_id' => 'sku_' . $customer->id . '_' . $p,
                        'title' => $sample[0],
                        'description' => 'High quality ' . $sample[0] . ' for everyday use.',
                        'price' => $sample[2],
                        'currency' => 'USD',
                        'availability' => 'in_stock',
                        'category' => $sample[1],
                        'created_at' => $now,
                        'updated_at' => $now,
                    ];
                }
                $catalog->update(['product_count' => $productCount]);

                // 10. Lead form configs — 1 Meta + 1 Google per customer
                $defaultList = $mailLists->first();
                $realisticLeadEmails = [
                    ['[email protected]', 'Alice Chen'],
                    ['[email protected]', 'Bob Martinez'],
                    ['[email protected]', 'Carol Wright'],
                    ['[email protected]', 'David Kim'],
                    ['[email protected]', 'Emma Wilson'],
                    ['[email protected]', 'Frank Ng'],
                    ['[email protected]', 'Grace Lee'],
                    ['[email protected]', 'Henry Okoro'],
                    ['[email protected]', 'Iris Patel'],
                    ['[email protected]', 'James Takeda'],
                ];

                foreach (['meta', 'google'] as $lfPlatform) {
                    $leadForm = AdLeadFormConfig::create([
                        'uid' => uniqid(),
                        'customer_id' => $customer->id,
                        'platform' => $lfPlatform,
                        'form_id' => $lfPlatform . '_form_' . str_pad((string) $customer->id, 6, '0', STR_PAD_LEFT),
                        'mail_list_id' => $defaultList?->id,
                        'field_mapping' => [
                            'email' => 'email',
                            'name' => 'first_name',
                            'phone' => 'phone',
                            'company' => 'company',
                        ],
                        'status' => 'active',
                        'leads_count' => mt_rand(3, 10),
                        'last_synced_at' => now()->subHours(mt_rand(1, 12)),
                    ]);
                    $leadFormsCreated++;

                    // 3-5 sample leads with realistic emails
                    $leadCount = mt_rand(3, 5);
                    $pickedEmails = array_rand($realisticLeadEmails, $leadCount);
                    if (!is_array($pickedEmails)) $pickedEmails = [$pickedEmails];
                    foreach ($pickedEmails as $emailIdx) {
                        $leadInfo = $realisticLeadEmails[$emailIdx];
                        $leadRows[] = [
                            'ad_lead_form_config_id' => $leadForm->id,
                            'platform_lead_id' => 'lead_' . $customer->id . '_' . $lfPlatform . '_' . $emailIdx,
                            'data' => json_encode([
                                'email' => $leadInfo[0],
                                'name' => $leadInfo[1],
                                'phone' => '+1-555-' . mt_rand(1000, 9999),
                                'company' => explode('@', $leadInfo[0])[1],
                            ]),
                            'synced_at' => now()->subHours(mt_rand(1, 48)),
                            'created_at' => $now,
                            'updated_at' => $now,
                        ];
                    }
                }

                // 11. Activity logs — rich mix of success, warning, error with
                // resolution hints (realistic demo shows both wins and failures).
                $activities = [
                    // Successes
                    ['platform.connected', 'success', 'Meta Business account connected', 'meta', null, null, null],
                    ['platform.connected', 'success', 'Google Ads account connected', 'google', null, null, null],
                    ['campaign.created', 'success', 'Created campaign "Summer Sale Promo"', null, null, null, null],
                    ['campaign.published', 'success', 'Campaign published to Meta (ID: act_123)', 'meta', null, null, null],
                    ['audience.synced', 'success', 'Audience synced to Facebook (12,450 users)', 'meta', null, null, null],
                    ['ab_test.started', 'info', 'A/B test started — Variant A vs Variant B', null, null, null, null],
                    ['pixel.verified', 'success', 'Meta Pixel verified (last 24h: 342 events)', 'meta', null, null, null],
                    ['conversion.tracked', 'info', 'Conversion tracked: Purchase $49.99', 'meta', null, null, null],

                    // Warnings
                    ['token.expiring_soon', 'warning', 'Meta access token expires in 5 days', 'meta', 'TOKEN_EXPIRING', 'Access token will expire on ' . now()->addDays(5)->format('M d'), 'Click "Reconnect" on the Platforms page to refresh your token before it expires.'],
                    ['audience.size_warning', 'warning', 'Audience "VIP Customers" below Meta minimum (87 users)', 'meta', 'AUDIENCE_TOO_SMALL', 'Audience has 87 users — Meta requires at least 100 for targeting.', 'Grow your list by 13+ subscribers or combine with another audience.'],

                    // Errors
                    ['campaign.publish_failed', 'error', 'Publish to Meta failed: Creative rejected', 'meta', 'CREATIVE_REJECTED', 'Meta rejected the primary image: logo size must be ≥200×200px.', 'Edit your creative and upload a larger logo. Then click "Retry Publish".'],
                    ['audience.sync_failed', 'error', 'Audience sync to Google failed', 'google', 'GOOGLE_API_RATE_LIMIT', 'Google Ads API rate limit exceeded (10,000 req/day).', 'Sync will auto-retry in 2 hours. Upgrade your Google Ads API quota for higher limits.'],
                ];
                $firstPlatform = reset($platforms);
                foreach ($activities as $a) {
                    if (!$firstPlatform) continue;
                    $activityLogRows[] = [
                        'uid' => uniqid(),
                        'customer_id' => $customer->id,
                        'loggable_type' => AdPlatform::class,
                        'loggable_id' => $firstPlatform->id,
                        'action' => $a[0],
                        'status' => $a[1],
                        'title' => $a[2],
                        'platform' => $a[3],
                        'error_code' => $a[4] ?? null,
                        'error_message' => $a[5] ?? null,
                        'resolution_hint' => $a[6] ?? null,
                        'created_at' => now()->subHours(mt_rand(1, 168)), // last 7 days
                    ];
                }
            }
        });

        // Flush collected rows in chunks (single INSERT per chunk → ~6 INSERTs
        // total instead of ~900 per-row Eloquent creates).
        $this->time('bulk-insert leaf ad tables', function () use (
            $metricRows, $conversionRows, $productRows, $leadRows, $publishLogRows, $activityLogRows
        ) {
            $bulk = [
                'ad_metrics'        => $metricRows,
                'ad_conversions'    => $conversionRows,
                'ad_products'       => $productRows,
                'ad_leads'          => $leadRows,
                'ad_publish_logs'   => $publishLogRows,
                'ad_activity_logs'  => $activityLogRows,
            ];
            foreach ($bulk as $table => $rows) {
                if (empty($rows)) continue;
                foreach (array_chunk($rows, 500) as $chunk) {
                    DB::table($table)->insert($chunk);
                }
                $this->log("bulk inserted " . count($rows) . " rows into {$table}");
            }
        });

        $this->log("seeded ads data: {$platformsCreated} platforms, {$campaignsCreated} campaigns, {$audiencesCreated} audiences, {$abTestsCreated} A/B tests, {$automationsCreated} automations, {$pixelsCreated} pixels, {$catalogsCreated} catalogs, {$leadFormsCreated} lead forms, {$metricsCreated} metrics");
    }
}