<?php

namespace Tests\Unit\AdOps;

use App\Services\AdOps\ListsService;
use PHPUnit\Framework\TestCase;

/**
 * ListsService has two public static helpers that run without the API client
 * or DB. They're the single ingestion path for user-supplied domains/keywords,
 * so regressions in either corrupt list contents silently.
 */
final class ListsServiceStaticsTest extends TestCase
{
    // ───────────────── parseDomainsBlob

    public function testParseDomainsBlobStripsProtocolAndPath(): void
    {
        $blob = "https://example.com/path?q=1\nhttp://Foo.Bar.io\nwww.baz.org";
        $domains = ListsService::parseDomainsBlob($blob);

        // Lowercased, protocol-stripped. The legacy regex extracts the first
        // domain match, which for "www.baz.org" is the full string.
        $this->assertContains('example.com', $domains);
        $this->assertContains('foo.bar.io', $domains);
        $this->assertContains('www.baz.org', $domains);
    }

    public function testParseDomainsBlobDeduplicates(): void
    {
        $blob = "example.com\nexample.com\nhttps://example.com/a\nhttps://example.com/b";
        $domains = ListsService::parseDomainsBlob($blob);

        $this->assertSame(1, count(array_filter($domains, fn($d) => $d === 'example.com')));
    }

    public function testParseDomainsBlobRejectsNonDomainStrings(): void
    {
        $blob = "notadomain\nlocalhost\n@@@\n12345";
        $domains = ListsService::parseDomainsBlob($blob);

        $this->assertSame([], $domains);
    }

    public function testParseDomainsBlobHandlesMixedLineEndings(): void
    {
        // CR/LF mix (Windows + Mac) must not create empty entries.
        $blob = "a.com\r\nb.com\rc.com\n";
        $domains = ListsService::parseDomainsBlob($blob);

        $this->assertContains('a.com', $domains);
        $this->assertContains('b.com', $domains);
        $this->assertContains('c.com', $domains);
        $this->assertCount(3, $domains);
    }

    // ───────────────── clearKeyword

    public function testClearKeywordStripsHttpAndLowercases(): void
    {
        $this->assertSame('example.com', ListsService::clearKeyword('HTTP://Example.com'));
        $this->assertSame('example.com', ListsService::clearKeyword('https://Example.com'));
    }

    public function testClearKeywordTrimsWhitespace(): void
    {
        $this->assertSame('buy shoes', ListsService::clearKeyword("   Buy Shoes\t\n"));
    }

    public function testClearKeywordLeavesPlainTextAlone(): void
    {
        $this->assertSame('running sneakers', ListsService::clearKeyword('Running Sneakers'));
    }

    public function testClearKeywordHandlesEmptyAndNull(): void
    {
        $this->assertSame('', ListsService::clearKeyword(''));
        $this->assertSame('', ListsService::clearKeyword(null));
    }

    // ───────────────── parseKeywordsBlob

    public function testParseKeywordsBlobKeepsTheDestinationUrlOffEachLine(): void
    {
        // Prod's format: one "keyword, destination url" per line, url optional.
        $rows = ListsService::parseKeywordsBlob("red shoes,https://example.com/red\nblue shoes");

        $this->assertSame(
            [
                ['keyword' => 'red shoes', 'dest_url' => 'https://example.com/red'],
                ['keyword' => 'blue shoes', 'dest_url' => ''],
            ],
            $rows
        );
    }

    public function testParseKeywordsBlobSplitsOnlyOnTheFirstComma(): void
    {
        // A url can carry its own commas; splitting on all of them used to turn
        // one line into several keywords, the url among them.
        $rows = ListsService::parseKeywordsBlob('boots,https://example.com/x?a=1,b=2');

        $this->assertCount(1, $rows);
        $this->assertSame('https://example.com/x?a=1,b=2', $rows[0]['dest_url']);
    }

    public function testParseKeywordsBlobDropsBlankLinesAndDeduplicates(): void
    {
        $rows = ListsService::parseKeywordsBlob("shoes,https://a.test/1\n\n   \nshoes,https://a.test/2");

        $this->assertCount(1, $rows);
        $this->assertSame('https://a.test/2', $rows[0]['dest_url'], 'last url given wins');
    }

    public function testParseKeywordsBlobReadsTheCsvsSecondColumnAsTheUrl(): void
    {
        $path = tempnam(sys_get_temp_dir(), 'kwblob') . '.csv';
        file_put_contents($path, "winter boots,https://example.com/boots\nsummer boots\n");

        $rows = ListsService::parseKeywordsBlob('', $path);
        @unlink($path);

        $this->assertSame(
            [
                ['keyword' => 'winter boots', 'dest_url' => 'https://example.com/boots'],
                ['keyword' => 'summer boots', 'dest_url' => ''],
            ],
            $rows
        );
    }

    public function testParseKeywordsBlobToleratesAMissingCsv(): void
    {
        $this->assertSame([], ListsService::parseKeywordsBlob('', '/no/such/file.csv'));
    }

    // ───────────────── validateListName

    public function testValidateListNameAcceptsAUsableName(): void
    {
        $this->assertNull(ListsService::validateListName('Brand Terms'));
        $this->assertNull(ListsService::validateListName(str_repeat('a', 50)), 'exactly at the limit');
    }

    public function testValidateListNameRejectsBlankAndWhitespaceOnly(): void
    {
        $this->assertSame('Name is required', ListsService::validateListName(''));
        $this->assertSame('Name is required', ListsService::validateListName("  \t "));
        $this->assertSame('Name is required', ListsService::validateListName(null));
    }

    public function testValidateListNameRejectsOverTheColumnLimit(): void
    {
        // varchar(50) — MySQL truncates silently outside strict mode, so the list
        // would come back under a different name than the one that was typed.
        $this->assertSame(
            'Name cannot be longer than 50 characters',
            ListsService::validateListName(str_repeat('a', 51))
        );
    }
}
