<?php

namespace App\Tests\Blog;

use App\Blog\Entity\Blog;
use App\Location\Entity\City;
use App\Location\Entity\Province;
use App\Tests\ApiTestCase;

/**
 * Blog tags live in a JSON column that Doctrine writes with plain json_encode, so
 * Persian names are stored \uXXXX-escaped. MariaDB's JSON_CONTAINS does not
 * normalize those escapes, which silently made every ?tag=<persian> query return
 * an empty list. These cases pin both the filter and the tag facet endpoint.
 *
 * db_test is never reset, so every case tags its fixtures with a random suffix and
 * asserts only on its own rows.
 */
class BlogTagFilterTest extends ApiTestCase
{
    private function makeCity(string $name): City
    {
        $province = new Province($name);
        $this->em->persist($province);
        $city = new City($name, $province);
        $this->em->persist($city);

        return $city;
    }

    /** @param string[] $tags */
    private function makePost(string $title, array $tags, ?City $city = null, string $status = Blog::STATUS_PUBLISHED): Blog
    {
        $blog = new Blog($this->createUser(['ROLE_ADMIN']), $title, 'متن آزمایشی مقاله برای تست');
        $blog->setStatus($status)->setCity($city)->setTags($tags);
        $this->em->persist($blog);

        return $blog;
    }

    /** @return array{titles: string[], total: int} */
    private function listBy(string $query): array
    {
        $this->client->request('GET', '/api/v1/blogs?limit=50&' . $query);
        $this->assertSame(200, $this->responseCode());
        $payload = json_decode($this->client->getResponse()->getContent(), true);

        return [
            'titles' => array_column($payload['data'], 'title'),
            'total'  => $payload['meta']['totalRecords'],
        ];
    }

    /** @return array<string, int> tag name => post count */
    private function facets(string $query = ''): array
    {
        $this->client->request('GET', '/api/v1/blogs/tags' . ($query !== '' ? '?' . $query : ''));
        $this->assertSame(200, $this->responseCode());
        $payload = json_decode($this->client->getResponse()->getContent(), true);

        return array_column($payload['data'], 'count', 'name');
    }

    public function testPersianTagFilterReturnsMatchingPosts(): void
    {
        $suffix = bin2hex(random_bytes(4));
        $eye    = "چشم و گوش-$suffix";
        $other  = "سلامت عمومی-$suffix";

        $this->makePost("هاله رنگی-$suffix", [$eye]);
        $this->makePost("خشکی چشم-$suffix", [$eye, $other]);
        $this->makePost("تغذیه-$suffix", [$other]);
        $this->em->flush();

        $result = $this->listBy('tag=' . rawurlencode($eye));

        $this->assertSame(2, $result['total'], 'meta.totalRecords must honour the tag filter too');
        $this->assertContains("هاله رنگی-$suffix", $result['titles']);
        $this->assertContains("خشکی چشم-$suffix", $result['titles']);
        $this->assertNotContains("تغذیه-$suffix", $result['titles']);
    }

    public function testUnknownTagReturnsEmptyListNotAnError(): void
    {
        $result = $this->listBy('tag=' . rawurlencode('برچسب-ناموجود-' . bin2hex(random_bytes(4))));

        $this->assertSame(0, $result['total']);
        $this->assertSame([], $result['titles']);
    }

    public function testTagMatchIsExactNotAPrefix(): void
    {
        $suffix = bin2hex(random_bytes(4));
        $this->makePost("مقاله-$suffix", ["سلامت عمومی-$suffix"]);
        $this->em->flush();

        $this->assertSame(0, $this->listBy('tag=' . rawurlencode('سلامت'))['total']);
    }

    public function testTagMatchDoesNotTreatUnderscoreOrPercentAsWildcard(): void
    {
        $suffix = bin2hex(random_bytes(4));
        $this->makePost("مقاله-$suffix", ["axb-$suffix"]);
        $this->em->flush();

        $this->assertSame(0, $this->listBy('tag=' . rawurlencode("a_b-$suffix"))['total']);
        $this->assertSame(0, $this->listBy('tag=' . rawurlencode("%-$suffix"))['total']);
    }

    public function testDraftPostsAreExcludedFromFilterAndFacets(): void
    {
        $suffix = bin2hex(random_bytes(4));
        $tag    = "پیش‌نویس-$suffix";

        $this->makePost("منتشرشده-$suffix", [$tag]);
        $this->makePost("پیش‌نویس-$suffix", [$tag], null, Blog::STATUS_DRAFT);
        $this->em->flush();

        $this->assertSame(1, $this->listBy('tag=' . rawurlencode($tag))['total']);
        $this->assertSame(1, $this->facets()[$tag] ?? 0);
    }

    public function testTagAndCityFiltersApplyTogether(): void
    {
        $suffix = bin2hex(random_bytes(4));
        $tag    = "چشم و گوش-$suffix";
        $yasuj  = $this->makeCity('یاسوج');
        $tabriz = $this->makeCity('تبریز');

        $this->makePost("یاسوجی-$suffix", [$tag], $yasuj);
        $this->makePost("تبریزی-$suffix", [$tag], $tabriz);
        $this->makePost("سراسری-$suffix", [$tag], null);
        $this->em->flush();

        $result = $this->listBy('tag=' . rawurlencode($tag) . '&city_id=' . $yasuj->getId());

        $this->assertSame(2, $result['total']);
        $this->assertContains("یاسوجی-$suffix", $result['titles']);
        $this->assertContains("سراسری-$suffix", $result['titles'], 'nationwide posts stay visible on a city domain');
        $this->assertNotContains("تبریزی-$suffix", $result['titles']);
    }

    public function testFacetsCountPublishedPostsPerTag(): void
    {
        $suffix = bin2hex(random_bytes(4));
        $eye    = "چشم و گوش-$suffix";
        $heart  = "قلب و عروق-$suffix";

        $this->makePost("اول-$suffix", [$eye]);
        $this->makePost("دوم-$suffix", [$eye, $heart]);
        $this->em->flush();

        $facets = $this->facets();

        $this->assertSame(2, $facets[$eye] ?? 0);
        $this->assertSame(1, $facets[$heart] ?? 0);
    }

    public function testFacetsHonourCityScope(): void
    {
        $suffix = bin2hex(random_bytes(4));
        $tag    = "چشم و گوش-$suffix";
        $yasuj  = $this->makeCity('یاسوج');
        $tabriz = $this->makeCity('تبریز');

        $this->makePost("یاسوجی-$suffix", [$tag], $yasuj);
        $this->makePost("تبریزی-$suffix", [$tag], $tabriz);
        $this->makePost("سراسری-$suffix", [$tag], null);
        $this->em->flush();

        $this->assertSame(3, $this->facets()[$tag] ?? 0, 'unscoped facets count every published post');
        $this->assertSame(2, $this->facets('city_id=' . $yasuj->getId())[$tag] ?? 0);
    }

    /**
     * Every facet count must equal the totalRecords of filtering by that same tag —
     * otherwise a chip in the public site leads to an empty result page.
     */
    public function testFacetCountMatchesFilteredTotal(): void
    {
        $suffix = bin2hex(random_bytes(4));
        $tag    = "غدد و متابولیسم-$suffix";

        $this->makePost("اول-$suffix", [$tag]);
        $this->makePost("دوم-$suffix", [$tag]);
        $this->makePost("سوم-$suffix", ["دیگر-$suffix"]);
        $this->em->flush();

        $this->assertSame(
            $this->facets()[$tag] ?? 0,
            $this->listBy('tag=' . rawurlencode($tag))['total']
        );
    }

    public function testPostsWithoutTagsProduceNoFacetEntry(): void
    {
        $suffix = bin2hex(random_bytes(4));
        $this->makePost("بدون برچسب-$suffix", []);
        $this->em->flush();

        $facets = $this->facets();

        $this->assertArrayNotHasKey('', $facets, 'an empty tag must never become a facet');
        $this->assertNotContains(0, $facets, 'no facet may report a zero count');
    }

    public function testFacetsEndpointIsPublic(): void
    {
        $this->client->request('GET', '/api/v1/blogs/tags');

        $this->assertSame(200, $this->responseCode(), 'the public site calls this without a token');
    }
}
