<?php

namespace Tests\Feature;

use App\Services\AdOps\EventLogService;
use App\Services\AdOps\ListsRepository;
use App\Services\AdOps\ListsService;
use CodeIgniter\Test\CIUnitTestCase;
use PHPUnit\Framework\Attributes\DataProvider;

/**
 * The entity string keyword-LIST events are written under has to be the one
 * Lists::getHistory reads for kind `keyword-lists`, or the popup can never show
 * them.
 *
 * ListsService logged list-level events as singular 'Keyword' while getHistory
 * reads `keyword-lists` as entity IN ('Keywords','package','Placement') and
 * routes 'Keyword' to kind `keywords` (where entity_id is a keyword ROW id).
 * Renaming a list therefore never appeared in that list's own history, and its
 * rows sat where the id would have been read as a keyword id. Legacy writes
 * 'Keywords' for Add/Update/Delete (lists/Keywords.php:167,371,390,611) and
 * reserves 'Keyword' for single-keyword deletes (:678).
 *
 * Both collaborators are constructor-injected, so this drives the real service
 * with a spy log and a stub repository — no DB, no HTTP.
 */
final class ListsHistoryEntityTest extends CIUnitTestCase
{
    /** Entity strings getHistory() accepts for kind `keyword-lists`. */
    private const READ_BY_KEYWORD_LISTS = ['Keywords', 'package', 'Placement'];

    private const LIST_ID = 4242;

    private function service(): array
    {
        $log = new class extends EventLogService {
            public array $rows = [];

            // Skip the parent's Database::connect('shorty').
            public function __construct() {}

            public function add(array $data)
            {
                $this->rows[] = $data;

                return true;
            }
        };

        $repo = new class extends ListsRepository {
            // No DB: every method the service calls here is overridden.
            public function __construct() {}

            public function createKeywordList(string $name, array $keywords, string $type, $bid = null): ?int
            {
                return ListsHistoryEntityTest::listId();
            }

            public function updateKeywordList(int $id, string $name, ?array $keywords, ?string $type, bool $setBid, $bid = null): bool
            {
                return true;
            }

            public function appendKeywordsToList(int $id, array $keywords): bool
            {
                return true;
            }

            public function deleteKeywordList(int $id): bool
            {
                return true;
            }

            public function getKeywordList(int $id): ?array
            {
                return ['id' => $id, 'name' => 'before'];
            }
        };

        return [new ListsService($repo, $log), $log];
    }

    public static function listId(): int
    {
        return self::LIST_ID;
    }

    #[DataProvider('listLevelWrites')]
    public function testListLevelEventsUseAnEntityTheHistoryPopupReads(string $label, callable $call): void
    {
        [$service, $log] = $this->service();

        $call($service);

        $this->assertCount(1, $log->rows, "$label should log exactly one row");
        $row = $log->rows[0];

        $this->assertContains(
            $row['entity'],
            self::READ_BY_KEYWORD_LISTS,
            "$label logged entity '{$row['entity']}', which getHistory() does not read for keyword-lists"
        );
        // The id has to be the LIST id, since that is what the popup queries by.
        $this->assertSame(self::LIST_ID, (int) $row['entity_id'], "$label should key on the list id");
    }

    public static function listLevelWrites(): array
    {
        return [
            'create' => ['createKeywordList', static function (ListsService $s) {
                $s->createKeywordList('a list', ['kw'], 'broad');
            }],
            'update' => ['updateKeywordList', static function (ListsService $s) {
                $s->updateKeywordList(self::LIST_ID, 'renamed', ['kw']);
            }],
            'append' => ['appendKeywordsToList', static function (ListsService $s) {
                $s->appendKeywordsToList(self::LIST_ID, ['kw']);
            }],
            'delete' => ['deleteKeywordList', static function (ListsService $s) {
                $s->deleteKeywordList(self::LIST_ID);
            }],
        ];
    }

    public function testRenamingLastsListIsVisibleUnderTheSameEntityLegacyUses(): void
    {
        // Narrow guard on the exact regression: legacy writes 'Keywords', so a
        // list edited in CI4 and one edited in legacy land in the same bucket.
        [$service, $log] = $this->service();

        $service->updateKeywordList(self::LIST_ID, 'renamed', ['kw']);

        $this->assertSame('Keywords', $log->rows[0]['entity']);
    }

    public function testKeywordRowEventsStayOnTheSingularEntity(): void
    {
        // The other half of the contract: per-keyword events must NOT move to
        // 'Keywords', or they would surface in list history keyed by a keyword id.
        [$service, $log] = $this->service();

        $service->deleteKeywords([11, 22]);

        $this->assertSame('Keyword', $log->rows[0]['entity']);
    }
}
