<?php

namespace Tests\Unit\Campaign;

use App\Services\Campaign\TargetingFromPost;
use App\Services\Campaign\TargetingJson;
use PHPUnit\Framework\TestCase;

/**
 * POST → targeting + clearedKeys. Merge must drop modeled empties and keep
 * unmodeled keys (affiliates) when the sentinel is absent.
 *
 * php8.2 -d newrelic.enabled=0 vendor/bin/phpunit tests/unit/Campaign/TargetingFromPostTest.php
 */
final class TargetingFromPostTest extends TestCase
{
    public function testAllConnectionRadioClearsEvenIfBoxesStillPosted(): void
    {
        $built = TargetingFromPost::fromPost([
            'targeting-connection-types' => '1',
            'connection-type'            => ['wifi', 'cellular'],
        ]);

        $this->assertContains('connection_types', $built->clearedKeys);
        $this->assertArrayNotHasKey('connection_types', $built->targeting);

        $merged = TargetingJson::mergeFormOntoExisting(
            $built->targeting,
            ['connection_types' => ['ethernet'], 'affiliates' => ['120438']],
            $built->clearedKeys
        );
        $this->assertArrayNotHasKey('connection_types', $merged);
        $this->assertSame(['120438'], $merged['affiliates']);
    }

    public function testSpecificConnectionSetsWhitelist(): void
    {
        $built = TargetingFromPost::fromPost([
            'targeting-connection-types' => '0',
            'connection-type'            => ['wifi', 'bogus'],
        ]);

        $this->assertSame(['wifi'], $built->targeting['connection_types']);
        $this->assertNotContains('connection_types', $built->clearedKeys);
    }

    public function testEmptyCategoriesWithModeClears(): void
    {
        $built = TargetingFromPost::fromPost([
            'targeting-categories_mode' => 'include',
        ]);

        $this->assertContains('categories', $built->clearedKeys);
        $merged = TargetingJson::mergeFormOntoExisting(
            $built->targeting,
            ['categories' => ['include' => ['IAB1-1']], 'affiliates' => ['120438']],
            $built->clearedKeys
        );
        $this->assertArrayNotHasKey('categories', $merged);
        $this->assertSame(['120438'], $merged['affiliates']);
    }

    public function testCategoriesSetIncludeAndExclude(): void
    {
        $inc = TargetingFromPost::fromPost([
            'targeting-categories_mode' => 'include',
            'categories'                => ['IAB1-1'],
        ]);
        $this->assertSame(['include' => ['IAB1-1']], $inc->targeting['categories']);

        $exc = TargetingFromPost::fromPost([
            'targeting-categories_mode' => 'exclude',
            'categories'                => ['IAB2-1'],
        ]);
        $this->assertSame(['exclude' => ['IAB2-1']], $exc->targeting['categories']);
    }

    public function testOmittingWithoutSentinelKeepsExistingOnMerge(): void
    {
        $built = TargetingFromPost::fromPost([]);

        $this->assertSame([], $built->clearedKeys);

        $merged = TargetingJson::mergeFormOntoExisting(
            $built->targeting,
            [
                'connection_types' => ['wifi'],
                'categories'       => ['include' => ['IAB1-1']],
                'browsers'         => ['chrome'],
                'affiliates'       => ['120438'],
                'pop_type'         => 'tab',
            ],
            $built->clearedKeys
        );

        $this->assertSame(['wifi'], $merged['connection_types']);
        $this->assertSame(['include' => ['IAB1-1']], $merged['categories']);
        $this->assertSame(['chrome'], $merged['browsers']);
        $this->assertSame(['120438'], $merged['affiliates']);
        $this->assertSame('tab', $merged['pop_type']);
    }

    public function testAllBrowsersRadioClearsEvenIfBoxesPosted(): void
    {
        $built = TargetingFromPost::fromPost([
            'targeting-browsers' => '1',
            'browsers'           => ['chrome', 'firefox'],
        ]);
        $this->assertContains('browsers', $built->clearedKeys);
        $this->assertArrayNotHasKey('browsers', $built->targeting);
    }

    public function testAllSuppliesWinsOverSpecificBoxes(): void
    {
        $built = TargetingFromPost::fromPost([
            'targeting-supplies' => ['all', 'web'],
        ]);
        $this->assertContains('supplies', $built->clearedKeys);
        $this->assertArrayNotHasKey('supplies', $built->targeting);
    }

    public function testSuppliesMapsMobileWebToLegacy(): void
    {
        $built = TargetingFromPost::fromPost([
            'targeting-supplies' => ['mobile_web', 'apps'],
        ]);
        $this->assertSame(['mobile', 'app'], $built->targeting['supplies']);
    }

    public function testScheduleOffClears(): void
    {
        $built = TargetingFromPost::fromPost([
            'targeting-schedule-on' => '0',
        ]);
        $this->assertContains('schedule', $built->clearedKeys);
    }

    public function testGenderAllClears(): void
    {
        $built = TargetingFromPost::fromPost([
            'targeting-gender' => 'all',
        ]);
        $this->assertContains('gender', $built->clearedKeys);
        $this->assertArrayNotHasKey('gender', $built->targeting);
    }

    public function testGenderMenSetsScalar(): void
    {
        $built = TargetingFromPost::fromPost([
            'targeting-gender' => 'men',
        ]);
        $this->assertSame(1, $built->targeting['gender']);
    }

    public function testEmptyDomainListsWithModeClears(): void
    {
        $built = TargetingFromPost::fromPost([
            'targeting-domain_lists_mode' => 'exclude',
        ]);
        $this->assertContains('domain_lists', $built->clearedKeys);
    }

    public function testEmptySourceListsWithSentinelClears(): void
    {
        $built = TargetingFromPost::fromPost([
            'source_lists_posted' => '1',
        ]);
        $this->assertContains('source_lists', $built->clearedKeys);
        $this->assertFalse($built->campaignCategoriesPosted);
        $this->assertFalse($built->creativesPosted);
        $this->assertNull($built->xmlTraffic);
    }

    public function testXmlTrafficAndCampaignCategoriesFromSentinels(): void
    {
        $empty = TargetingFromPost::fromPost([
            'camp_category_ids_posted' => '1',
            'xml_traffic'              => '0',
        ]);
        $this->assertTrue($empty->campaignCategoriesPosted);
        $this->assertSame([], $empty->campaignCategoryIds);
        $this->assertSame(0, $empty->xmlTraffic);

        $set = TargetingFromPost::fromPost([
            'camp_category_ids_posted' => '1',
            'camp_category_ids'        => ['12', '0', '34'],
            'xml_traffic'              => '1',
        ]);
        $this->assertSame([12, 34], $set->campaignCategoryIds);
        $this->assertSame(1, $set->xmlTraffic);
    }

    public function testCreativesSentinelSetAndClear(): void
    {
        $this->assertFalse(TargetingFromPost::fromPost([])->creativesPosted);

        $empty = TargetingFromPost::fromPost([
            'creatives_posted' => '1',
        ]);
        $this->assertTrue($empty->creativesPosted);
        $this->assertSame([], $empty->creativeIds);

        $set = TargetingFromPost::fromPost([
            'creatives_posted' => '1',
            'creatives'        => ['12', '0', '34'],
        ]);
        $this->assertTrue($set->creativesPosted);
        $this->assertSame([12, 34], $set->creativeIds);
    }

    public function testFrequencyNoneClearsAndIsModeled(): void
    {
        $built = TargetingFromPost::fromPost([
            'targeting-frequency-mode' => 'none',
            'targeting-frequency'      => '1',
        ]);
        $this->assertTrue($built->frequencyModeled);
        $this->assertContains('frequency', $built->clearedKeys);
        $this->assertArrayNotHasKey('frequency', $built->targeting);
        $this->assertSame('', $built->freqNote);
    }

    public function testFrequencySetZeroBecomesUnlimitedNote(): void
    {
        $built = TargetingFromPost::fromPost([
            'targeting-frequency-mode' => 'set',
            'targeting-frequency'      => '0',
        ]);
        $this->assertTrue($built->frequencyModeled);
        $this->assertContains('frequency', $built->clearedKeys);
        $this->assertSame(TargetingFromPost::FREQ_ZERO_NOTE, $built->freqNote);
    }

    public function testKeywordListsPostedEmptyClearsWholeObject(): void
    {
        $built = TargetingFromPost::fromPost([
            'keyword_lists_posted' => '1',
        ]);
        $this->assertContains('keyword_lists', $built->clearedKeys);

        $oneSide = TargetingFromPost::fromPost([
            'keyword_lists_posted' => '1',
            'keyword_lists'        => ['9'],
        ]);
        $this->assertSame(['include' => [9]], $oneSide->targeting['keyword_lists']);
        $this->assertArrayNotHasKey('exclude', $oneSide->targeting['keyword_lists']);
    }

    public function testLocationPostedEmptyGeoGoesToClearedKeys(): void
    {
        $built = TargetingFromPost::fromPost([
            'location_posted' => '1',
        ]);
        $this->assertSame(['USA'], $built->targeting['countries']);
        $this->assertContains('states', $built->clearedKeys);
        $this->assertContains('metro', $built->clearedKeys);
        $this->assertContains('cities', $built->clearedKeys);
        $this->assertContains('zip', $built->clearedKeys);
    }

    public function testGeoDisabledClearsStoredGeoOnMerge(): void
    {
        $built = TargetingFromPost::fromPost([
            'location_posted' => '1',
            'countries'       => ['US'],
            'states'          => ['CA'],
        ], true);

        $this->assertContains('countries', $built->clearedKeys);
        $this->assertContains('states', $built->clearedKeys);

        $merged = TargetingJson::mergeFormOntoExisting(
            $built->targeting,
            [
                'countries' => ['USA'],
                'states'    => ['NY'],
                'exclude'   => ['countries' => ['CAN']],
                'affiliates' => ['120438'],
            ],
            $built->clearedKeys
        );
        $this->assertArrayNotHasKey('countries', $merged);
        $this->assertArrayNotHasKey('states', $merged);
        $this->assertSame([], $merged['exclude']['countries']);
        $this->assertSame(['120438'], $merged['affiliates']);
    }
}
