<?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 posts carry an optional city so the multi-domain public site can scope
 * them. NULL is a permanent, meaningful state — a nationwide post, canonical on
 * the main domain but listed on every city domain.
 */
class BlogCityScopeTest 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;
    }

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

        return $blog;
    }

    /** @return array<string, array> published list keyed by title */
    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);

        $byTitle = [];
        foreach ($payload['data'] as $row) {
            $byTitle[$row['title']] = $row;
        }

        return $byTitle;
    }

    public function testCityScopedListReturnsCityPostsPlusNationwide(): void
    {
        $yasuj  = $this->makeCity('یاسوج');
        $tabriz = $this->makeCity('تبریز');
        $tag    = bin2hex(random_bytes(4));

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

        $scoped = $this->listBy('&city_id=' . $yasuj->getId());

        $this->assertArrayHasKey("یاسوجی-$tag", $scoped, 'city post missing');
        $this->assertArrayHasKey("سراسری-$tag", $scoped, 'nationwide post must appear on a city domain');
        $this->assertArrayNotHasKey("تبریزی-$tag", $scoped, 'another city\'s post leaked into the list');
    }

    public function testUnscopedListReturnsEveryPublishedPost(): void
    {
        $yasuj = $this->makeCity('یاسوج');
        $tag   = bin2hex(random_bytes(4));

        $this->makePost("یاسوجی-$tag", $yasuj);
        $this->makePost("سراسری-$tag", null);
        $this->em->flush();

        $all = $this->listBy();

        $this->assertArrayHasKey("یاسوجی-$tag", $all);
        $this->assertArrayHasKey("سراسری-$tag", $all);
    }

    public function testCityAppearsInListAndDetailPayload(): void
    {
        $yasuj = $this->makeCity('یاسوج');
        $tag   = bin2hex(random_bytes(4));

        $cityPost = $this->makePost("یاسوجی-$tag", $yasuj);
        $natPost  = $this->makePost("سراسری-$tag", null);
        $this->em->flush();

        $list = $this->listBy();
        $this->assertSame(
            ['id' => (string) $yasuj->getId(), 'name' => 'یاسوج'],
            $list["یاسوجی-$tag"]['city']
        );
        $this->assertNull($list["سراسری-$tag"]['city'], 'nationwide post must report city: null');

        $this->client->request('GET', '/api/v1/blog/' . $cityPost->getSlug());
        $this->assertSame(200, $this->responseCode());
        $detail = json_decode($this->client->getResponse()->getContent(), true)['data']['data'];
        $this->assertSame('یاسوج', $detail['city']['name']);

        $this->client->request('GET', '/api/v1/blog/' . $natPost->getSlug());
        $this->assertSame(200, $this->responseCode());
        $natDetail = json_decode($this->client->getResponse()->getContent(), true)['data']['data'];
        $this->assertNull($natDetail['city']);
    }

    /**
     * The detail endpoint must scope exactly like the list: a post owned by
     * another city does not exist on this domain. Without this, a post filtered
     * out of /api/v1/blogs?city_id=… was still served with a direct URL, so the
     * same article appeared on every city domain.
     */
    public function testDetailIsNotFoundOnAnotherCityDomain(): void
    {
        $yasuj  = $this->makeCity('یاسوج');
        $tabriz = $this->makeCity('تبریز');
        $post   = $this->makePost('یاسوجی ' . bin2hex(random_bytes(3)), $yasuj);
        $this->em->flush();

        $this->client->request('GET', '/api/v1/blog/' . $post->getSlug() . '?city_id=' . $tabriz->getId());
        $this->assertSame(404, $this->responseCode(), 'another city\'s post must not be readable');
    }

    public function testDetailIsFoundOnItsOwnCityDomain(): void
    {
        $yasuj = $this->makeCity('یاسوج');
        $post  = $this->makePost('یاسوجی ' . bin2hex(random_bytes(3)), $yasuj);
        $this->em->flush();

        $this->client->request('GET', '/api/v1/blog/' . $post->getUuid() . '?city_id=' . $yasuj->getId());
        $this->assertSame(200, $this->responseCode());
    }

    public function testNationwideDetailIsReadableFromEveryCityDomain(): void
    {
        $tabriz = $this->makeCity('تبریز');
        $post   = $this->makePost('سراسری ' . bin2hex(random_bytes(3)), null);
        $this->em->flush();

        $this->client->request('GET', '/api/v1/blog/' . $post->getSlug() . '?city_id=' . $tabriz->getId());
        $this->assertSame(200, $this->responseCode(), 'nationwide posts stay visible on city domains');
    }

    public function testDetailWithoutCityIdStaysUnscoped(): void
    {
        $yasuj = $this->makeCity('یاسوج');
        $post  = $this->makePost('یاسوجی ' . bin2hex(random_bytes(3)), $yasuj);
        $this->em->flush();

        // The main domain (and any API consumer) omits city_id and sees everything.
        $this->client->request('GET', '/api/v1/blog/' . $post->getSlug());
        $this->assertSame(200, $this->responseCode());

        $this->client->request('GET', '/api/v1/blog/' . $post->getSlug() . '?city_id=');
        $this->assertSame(200, $this->responseCode(), 'an empty city_id must not scope the request');
    }

    public function testAdminCanCreatePostWithAndWithoutCity(): void
    {
        $admin = $this->createUser(['ROLE_ADMIN']);
        $yasuj = $this->makeCity('یاسوج');
        $this->em->flush();

        $withCity = $this->authJson('POST', '/api/v1/blog', $admin, [
            'title'   => 'مقاله شهری ' . bin2hex(random_bytes(3)),
            'body'    => 'متن آزمایشی مقاله برای تست',
            'city_id' => $yasuj->getId(),
        ]);
        $this->assertSame(201, $this->responseCode());
        $this->assertSame('یاسوج', $withCity['data']['data']['city']['name']);

        $nationwide = $this->authJson('POST', '/api/v1/blog', $admin, [
            'title' => 'مقاله سراسری ' . bin2hex(random_bytes(3)),
            'body'  => 'متن آزمایشی مقاله برای تست',
        ]);
        $this->assertSame(201, $this->responseCode());
        $this->assertNull($nationwide['data']['data']['city'], 'omitting city_id must mean nationwide');
    }

    public function testAdminCanMovePostBetweenCityAndNationwide(): void
    {
        $admin = $this->createUser(['ROLE_ADMIN']);
        $yasuj = $this->makeCity('یاسوج');
        $post  = $this->makePost('مقاله ' . bin2hex(random_bytes(3)), null);
        $this->em->flush();

        $assigned = $this->authJson('PATCH', '/api/v1/blog/' . $post->getUuid(), $admin, [
            'city_id' => $yasuj->getId(),
        ]);
        $this->assertSame(200, $this->responseCode());
        $this->assertSame('یاسوج', $assigned['data']['data']['city']['name']);

        $cleared = $this->authJson('PATCH', '/api/v1/blog/' . $post->getUuid(), $admin, [
            'city_id' => null,
        ]);
        $this->assertSame(200, $this->responseCode());
        $this->assertNull($cleared['data']['data']['city'], 'city_id: null must make the post nationwide again');
    }

    public function testPatchWithoutCityIdLeavesCityUntouched(): void
    {
        $admin = $this->createUser(['ROLE_ADMIN']);
        $yasuj = $this->makeCity('یاسوج');
        $post  = $this->makePost('مقاله ' . bin2hex(random_bytes(3)), $yasuj);
        $this->em->flush();

        $updated = $this->authJson('PATCH', '/api/v1/blog/' . $post->getUuid(), $admin, [
            'summary' => 'خلاصه جدید',
        ]);
        $this->assertSame(200, $this->responseCode());
        $this->assertSame('یاسوج', $updated['data']['data']['city']['name'], 'PATCH must not silently clear the city');
    }

    public function testUnknownCityIdIsRejected(): void
    {
        $admin = $this->createUser(['ROLE_ADMIN']);
        $this->em->flush();

        $this->authJson('POST', '/api/v1/blog', $admin, [
            'title'   => 'مقاله ' . bin2hex(random_bytes(3)),
            'body'    => 'متن آزمایشی مقاله برای تست',
            'city_id' => 999999,
        ]);
        $this->assertSame(422, $this->responseCode());
    }
}
