<?php

namespace Tests\Feature;

use App\Agent\Instructions;
use App\Campaigns\Publishing\GooglePublisher;
use App\Campaigns\Publishing\LinkedInPublisher;
use App\Campaigns\Publishing\MetaPublisher;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

/**
 * The prompt says which platforms can be published to by reading, not by memory.
 *
 * The sentence was "Only Meta can be published to today", typed into the system
 * prompt. Google became publishable and the sentence stayed, so on 23 Sep a
 * buyer partway through a Google campaign that was talking to the live API was
 * told "Google campaigns cannot be published yet because Google Ads is not
 * connected". The model was not wrong to say it: it was repeating what we told
 * it.
 *
 * A fact about configuration in prose that nobody updates is a lie waiting for
 * the config to change. This reads config('platforms.publishers') and then asks
 * each platform whether it actually has credentials, because a publisher class
 * existing is not the same as the platform working.
 */
class PublishablePlatformsAreNotHardcodedTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();

        $this->actingAs(User::factory()->create());

        // Credentials for every platform these tests claim is publishable.
        //
        // They used to assert the sentence named Google Ads and LinkedIn while
        // neither had any credentials under test, and it did, because the
        // sentence was built from the publisher list alone. Setting what the
        // assertion claims is what makes it an assertion about behaviour rather
        // than about a config array.
        $this->configure();
    }

    private function configure(): void
    {
        config([
            'platforms.meta.token' => 'meta-token',
            'platforms.linkedin.token' => 'linkedin-token',
            'services.google_ads.refresh_token' => 'google-refresh-token',
            'services.google_ads.customer_id' => '3460855874',
        ]);
    }

    /**
     * The whole sentence, not its words scattered anywhere in the prompt.
     *
     * LinkedIn joined the publishers and this assertion was split into three
     * independent substring checks to make it pass again. Split like that it
     * proves nothing: "Meta and LinkedIn can be published to" passes it as
     * long as the words "Google Ads" appear somewhere else in four thousand
     * words of prompt, which they do. The sentence being contiguous and
     * correct is the only thing this file exists to check.
     */
    public function test_it_names_every_configured_publisher(): void
    {
        $this->assertStringContainsString(
            'Meta, Google Ads, and LinkedIn can be published to',
            app(Instructions::class)->build(),
        );
    }

    /**
     * Three or more read as a list, which is its own branch.
     *
     * Two platforms join with "and" and three with commas and a final "and",
     * and that separation was rewritten when LinkedIn arrived with nothing
     * covering the longer form.
     */
    public function test_three_publishers_are_joined_as_a_list(): void
    {
        config(['platforms.publishers' => [
            'meta' => MetaPublisher::class,
            'google' => GooglePublisher::class,
            'linkedin' => LinkedInPublisher::class,
        ]]);

        $this->assertStringContainsString(
            'Meta, Google Ads, and LinkedIn can be published to',
            app(Instructions::class)->build(),
        );
    }

    /** Two join with "and" alone, with no stray comma. */
    public function test_two_publishers_are_joined_with_and(): void
    {
        config(['platforms.publishers' => [
            'meta' => MetaPublisher::class,
            'google' => GooglePublisher::class,
        ]]);

        $this->assertStringContainsString(
            'Meta and Google Ads can be published to',
            app(Instructions::class)->build(),
        );
    }

    public function test_the_old_hardcoded_sentence_is_gone(): void
    {
        $this->assertStringNotContainsString(
            'Only Meta can be published to',
            app(Instructions::class)->build(),
            'The prompt still claims Meta is the only publishable platform.',
        );
    }

    /**
     * Removing a publisher changes the sentence.
     *
     * This is the half that matters: asserting the current wording only proves
     * someone typed it correctly once.
     */
    public function test_it_follows_the_configuration(): void
    {
        config(['platforms.publishers' => ['meta' => MetaPublisher::class]]);

        $text = app(Instructions::class)->build();

        $this->assertStringContainsString('Meta can be published to', $text);
        $this->assertStringNotContainsString('Google Ads can be published to', $text);
    }

    /**
     * A publisher with no credentials is not somewhere you can publish.
     *
     * This is the defect the whole file exists to catch, arriving by a door it
     * did not cover. LinkedIn's publisher class was written, so LinkedIn joined
     * the sentence, and an environment with no LinkedIn token at all was told
     * "Meta, Google Ads, and LinkedIn can be published to". The model then
     * offered a platform whose first API call would have failed on auth.
     */
    public function test_a_publisher_without_credentials_is_not_named(): void
    {
        config(['platforms.linkedin.token' => null]);

        $text = app(Instructions::class)->build();

        $this->assertStringContainsString('Meta and Google Ads can be published to', $text);
        $this->assertStringNotContainsString('and LinkedIn can be published to', $text);
    }

    /** With nothing configured at all it says so, rather than naming classes. */
    public function test_nothing_configured_is_said_plainly(): void
    {
        config([
            'platforms.meta.token' => null,
            'platforms.linkedin.token' => null,
            'services.google_ads.refresh_token' => null,
        ]);

        $this->assertStringContainsString(
            'No platform can be published to',
            app(Instructions::class)->build(),
        );
    }

    /** Google's account is not something to ask the buyer for. */
    public function test_it_tells_the_model_not_to_ask_which_google_account(): void
    {
        $text = app(Instructions::class)->build();

        $this->assertStringContainsString('never ask which Google account to use', $text);
        $this->assertStringContainsString('never call verify_ad_account for', $text);
    }

    /**
     * Nor keywords, which the product reads from the page.
     *
     * Seen on dev on 23 Sep: "Please provide the keywords to target for this
     * Google search campaign", asked of a buyer whose brief already held ten
     * keywords read from the landing page. GooglePublisher takes them from
     * there, nothing stores an answer to that question, and typing a list into
     * the chat would have changed nothing.
     */
    public function test_it_tells_the_model_not_to_ask_for_keywords(): void
    {
        // Asserted on one line of the prompt: the sentence wraps, so matching
        // across the break tests the indentation rather than the instruction.
        $this->assertStringContainsString(
            'keywords are read from the landing page, not asked for',
            app(Instructions::class)->build(),
        );
    }
}
