<?php

namespace Tests\Feature;

use Tests\TestCase;

/**
 * .env.example is the only instruction an admin gets.
 *
 * It is not documentation, it is the file somebody copies and fills in, so a
 * key that is missing from it is a setting nobody knows to set, and a key
 * declared twice is worse: the later definition wins, so the obvious one gets
 * filled in and silently ignored.
 *
 * Found by testing rather than by reading: GOOGLE_ADS_DEVELOPER_TOKEN and
 * GOOGLE_ADS_LOGIN_CUSTOMER_ID were each declared twice, once for the campaign
 * API and once for the keyword planner, so filling in the campaign block did
 * nothing and Google Ads publishing would have failed with an empty developer
 * token and no clue why.
 */
class EnvExampleIsUsableTest extends TestCase
{
    /** @return list<string> */
    private function keys(): array
    {
        preg_match_all('/^([A-Z0-9_]+)=/m', (string) file_get_contents(base_path('.env.example')), $found);

        return $found[1];
    }

    /**
     * Keys a reader can find, set or commented out with its default.
     *
     * An optional override is properly documented as a commented line - that is
     * the convention the Whale and performance-sync blocks already use - so
     * requiring every key to be live would push secrets and irrelevant overrides
     * into a file people copy wholesale.
     *
     * @return list<string>
     */
    private function mentionedKeys(): array
    {
        preg_match_all(
            '/^#?\s*([A-Z0-9_]+)=/m',
            (string) file_get_contents(base_path('.env.example')),
            $found,
        );

        return $found[1];
    }

    /**
     * Every key a config file reads is findable in .env.example.
     *
     * @return list<string>
     */
    private function keysReadBy(string $configFile): array
    {
        preg_match_all(
            "/env\(\s*'([A-Z0-9_]+)'/",
            (string) file_get_contents(base_path('config/'.$configFile)),
            $found,
        );

        return array_values(array_unique($found[1]));
    }

    /** A key declared twice is a key one of whose values is thrown away. */
    public function test_no_key_is_declared_twice(): void
    {
        $counts = array_count_values($this->keys());
        $duplicates = array_keys(array_filter($counts, fn (int $n): bool => $n > 1));

        $this->assertSame([], $duplicates, 'Declared more than once; the later definition silently wins.');
    }

    /**
     * Everything the Google integrations read has somewhere to be set.
     *
     * Both of them: the campaign API in config/services.php and the keyword
     * planner in config/google-ads.php. They are separate integrations with
     * separate OAuth clients that happen to share a developer token, which is
     * exactly why the duplicates arose.
     */
    public function test_every_google_setting_has_a_home(): void
    {
        $keys = $this->keys();

        foreach ([
            'GOOGLE_ADS_DEVELOPER_TOKEN',
            'GOOGLE_ADS_OAUTH_CLIENT_ID',
            'GOOGLE_ADS_OAUTH_CLIENT_SECRET',
            'GOOGLE_ADS_REFRESH_TOKEN',
            'GOOGLE_ADS_LOGIN_CUSTOMER_ID',
            'GOOGLE_ADS_CUSTOMER_ID',
            'GOOGLE_ADS_CLIENT_ID',
            'GOOGLE_ADS_CLIENT_SECRET',
        ] as $key) {
            $this->assertContains($key, $keys, "{$key} is read by config but cannot be set.");
        }
    }

    /**
     * Every ad-platform and guardrail setting is findable.
     *
     * Checked against the config file rather than against a list, because a list
     * is what let this drift: 29 of the 31 keys config/platforms.php reads were
     * undocumented, including META_ACCESS_TOKEN. The cost was not theoretical -
     * a developer setting the product up on 29 Sep hit "Provide valid app ID" on
     * every Meta call and had to ask which constants to add, because the file he
     * copied did not mention Meta at all.
     *
     * PLATFORM_ALLOWED_AD_ACCOUNTS mattered more than the rest: it is the list of
     * accounts this install may touch, it decides what can spend money, and it
     * could not be discovered without reading the source of the thing it governs.
     *
     * LINKEDIN_PUBLISH_ENABLED was named here too until it was deleted. It decided
     * whether LinkedIn campaigns went live, which is now an approved decision with
     * a tool behind it rather than a property of the environment.
     */
    public function test_every_platform_and_guardrail_setting_has_a_home(): void
    {
        $mentioned = $this->mentionedKeys();
        $missing = [];

        foreach ($this->keysReadBy('platforms.php') as $key) {
            if (! in_array($key, $mentioned, true)) {
                $missing[] = $key;
            }
        }

        $this->assertSame([], $missing, 'Read by config/platforms.php but absent from .env.example.');
    }

    /** The settings that decide what can spend money are live lines, not comments. */
    public function test_the_settings_that_gate_spending_are_not_merely_commented(): void
    {
        foreach ([
            'PLATFORM_ALLOWED_AD_ACCOUNTS',
            'PLATFORM_MAX_DAILY_BUDGET',
            'PLATFORM_CONFIRM_DAILY_BUDGET_ABOVE',
            'META_ACCESS_TOKEN',
        ] as $key) {
            $this->assertContains(
                $key,
                $this->keys(),
                "{$key} decides what can reach an ad platform and should be a line somebody fills in.",
            );
        }
    }

    /**
     * A flag that changes who can read whose chats has to be discoverable.
     *
     * Undocumented, the only way to learn it exists is to read the source of
     * the thing it turns off.
     */
    public function test_the_chat_privacy_flag_is_documented(): void
    {
        $this->assertContains('ARB_SHARED_CONVERSATIONS', $this->keys());
    }
}
