<?php

namespace Tests\Feature;

use App\Campaigns\PendingChanges;
use App\Campaigns\Targeting\TargetingSelections;
use App\Models\Campaign;
use App\Models\Location;
use App\Models\PlatformLocation;
use App\Models\TargetingSet;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use RuntimeException;
use Tests\TestCase;

class MetaLocationTargetingTest extends TestCase
{
    use RefreshDatabase;

    private function campaign(array $countries = ['US']): Campaign
    {
        return Campaign::create([
            'user_id' => User::factory()->create()->getKey(),
            'name' => 'Local campaign',
            'platform' => 'meta',
            'status' => 'draft',
            'locations' => ['countries' => $countries],
        ]);
    }

    private function mapping(string $country, string $type, string $id): PlatformLocation
    {
        $location = Location::factory()->create([
            'type' => $type,
            'name' => $type === 'city' ? 'Springfield' : 'California',
            'country_code' => $country,
            'iso_code' => null,
            'source' => 'meta',
            'source_key' => $id,
        ]);

        return PlatformLocation::factory()->create([
            'location_id' => $location->getKey(),
            'platform' => 'meta',
            'external_id' => $id,
            'provider_name' => $location->name.', '.$country,
        ]);
    }

    public function test_a_selected_city_narrows_only_its_own_country(): void
    {
        $campaign = $this->campaign(['US', 'GB']);
        $mapping = $this->mapping('US', 'city', '123');
        $selections = app(TargetingSelections::class);

        $set = $selections->addCampaignLocation($campaign, $mapping, expectedRevision: 0);

        $this->assertSame(1, $set->revision);
        $this->assertSame(['countries' => ['GB'], 'cities' => [['key' => '123']]], $selections->metaGeoLocations($campaign->fresh()));
        $this->assertSame(['US', 'GB'], $campaign->fresh()->locations['countries']);
    }

    public function test_selected_region_uses_its_meta_key_and_country_changes_are_blocked(): void
    {
        $campaign = $this->campaign();
        $selections = app(TargetingSelections::class);
        $selections->addCampaignLocation($campaign, $this->mapping('US', 'region', '4081'));

        $this->assertSame(['regions' => [['key' => '4081']]], $selections->metaGeoLocations($campaign->fresh()));

        $selections->saveCampaignCountries($campaign->fresh(), ['GB']);

        $this->assertStringContainsString('invalid', $selections->locationProblems($campaign->fresh())[0]);
    }

    public function test_selected_place_is_visible_in_the_campaign_review_panel(): void
    {
        $campaign = $this->campaign();
        app(TargetingSelections::class)->addCampaignLocation($campaign, $this->mapping('US', 'region', '4081'));

        $html = view('partials.campaign-panel', [
            'campaign' => $campaign->fresh(),
            'progress' => [],
            'nextStep' => null,
            'blocking' => [],
            'sources' => [],
            'pending' => [],
            'needsRebuild' => [],
            'campaigns' => collect([$campaign]),
            'assets' => collect(),
        ])->render();

        $this->assertStringContainsString('Cities / regions', $html);
        $this->assertStringContainsString('California, US', $html);
        $this->assertStringContainsString('US (selected places only)', $html);
    }

    public function test_stale_or_wrong_country_mapping_cannot_be_attached(): void
    {
        $campaign = $this->campaign();
        $selections = app(TargetingSelections::class);
        $selections->addCampaignLocation($campaign, $this->mapping('US', 'region', '111'), expectedRevision: 0);

        try {
            $selections->addCampaignLocation($campaign->fresh(), $this->mapping('US', 'city', '222'), expectedRevision: 0);
            $this->fail('A stale revision was accepted.');
        } catch (RuntimeException $exception) {
            $this->assertStringContainsString('changed', $exception->getMessage());
        }

        try {
            $selections->addCampaignLocation($campaign->fresh(), $this->mapping('CA', 'city', '333'));
            $this->fail('A place outside the selected countries was accepted.');
        } catch (RuntimeException $exception) {
            $this->assertStringContainsString('within', $exception->getMessage());
        }

        $this->assertSame(1, $campaign->fresh()->targetingSet->locations()->where('kind', '!=', 'country')->count());
    }

    public function test_expired_mapping_blocks_publishing_instead_of_falling_back_to_whole_country(): void
    {
        $campaign = $this->campaign();
        $mapping = $this->mapping('US', 'city', '123');
        $selections = app(TargetingSelections::class);
        $selections->addCampaignLocation($campaign, $mapping);
        $mapping->update(['verified_at' => now()->subDays(31)]);

        $this->assertStringContainsString('out of date', $selections->locationProblems($campaign->fresh())[0]);
    }

    public function test_imported_provider_geo_cannot_be_silently_republished_from_a_draft(): void
    {
        $campaign = $this->campaign();
        $campaign->update(['locations' => [
            'countries' => ['US'],
            'meta_geo_locations' => ['cities' => [['key' => 'old-city']]],
        ]]);

        $this->assertStringContainsString('reselected', app(TargetingSelections::class)->locationProblems($campaign->fresh())[0]);
    }

    public function test_a_published_campaign_rejects_location_edits_and_detects_direct_changes(): void
    {
        $campaign = $this->campaign();
        $mapping = $this->mapping('US', 'region', '111');
        $pending = app(PendingChanges::class);
        $campaign->update([
            'status' => 'published',
            'external_campaign_id' => 'external-1',
            'published_state' => $pending->snapshot($campaign),
        ]);

        $this->expectException(RuntimeException::class);

        try {
            app(TargetingSelections::class)->addCampaignLocation($campaign->fresh(), $mapping);
        } finally {
            $campaign->update(['locations' => ['countries' => ['GB']]]);
            $this->assertContains('location targeting changed, which needs a new campaign', $pending->requiringRebuild($campaign->fresh()));
            $this->assertSame(0, TargetingSet::query()->count());
        }
    }
}
