<?php

namespace App\Tests\Blog;

use App\Blog\Entity\Blog;
use App\Blog\Service\ScheduledBlogPublisher;
use App\Location\Entity\City;
use App\Location\Entity\Province;
use App\Representation\Entity\Representation;
use App\Tests\ApiTestCase;

/**
 * v2 backend: SEO fields, representative-scoped ownership, and scheduled publish.
 */
class BlogV2FieldsTest 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 makeRepresentation(array $cities): Representation
    {
        $user = $this->createUser(['ROLE_REPRESENTATION']);
        $rep = new Representation($user, 'نمایندهٔ ' . bin2hex(random_bytes(3)));
        $rep->setCities($cities);
        $this->em->persist($rep);
        $this->em->flush();
        return $rep;
    }

    // ── SEO fields (admin path) ───────────────────────────────────────────────

    public function testAdminCreateStoresSeoFields(): void
    {
        $admin = $this->createUser(['ROLE_ADMIN']);
        $res = $this->authJson('POST', '/api/v1/blog', $admin, [
            'title' => 'مقالهٔ سئو ' . bin2hex(random_bytes(3)),
            'body'  => 'متن آزمایشی مقاله برای تست',
            'meta_title' => 'عنوان متا',
            'meta_description' => 'توضیح متا برای موتور جستجو',
            'primary_keyword' => 'سکته قلبی',
            'secondary_keywords' => ['علائم', 'پیشگیری', ''],
            'faq' => [['q' => 'سؤال؟', 'a' => 'جواب'], ['q' => '', 'a' => 'بی‌سؤال']],
            'internal_links' => ['https://nobat724.com/x'],
            'reading_time' => 6,
        ]);
        $this->assertSame(201, $this->responseCode());
        $b = $res['data']['data'];
        $this->assertSame('توضیح متا برای موتور جستجو', $b['meta_description']);
        $this->assertSame('سکته قلبی', $b['primary_keyword']);
        $this->assertSame(['علائم', 'پیشگیری'], $b['secondary_keywords'], 'empty keyword dropped');
        $this->assertCount(1, $b['faq'], 'malformed FAQ entry dropped');
        $this->assertSame(6, $b['reading_time']);
    }

    // ── Admin list shows drafts (public list does not) ────────────────────────

    public function testAdminListReturnsDraftsThatPublicListHides(): void
    {
        $admin = $this->createUser(['ROLE_ADMIN']);
        $tag   = bin2hex(random_bytes(4));

        $draft = new Blog($admin, "پیش‌نویس-$tag", 'متن آزمایشی مقاله برای تست');
        $draft->setStatus(Blog::STATUS_DRAFT);
        $pub = new Blog($admin, "منتشر-$tag", 'متن آزمایشی مقاله برای تست');
        $pub->setStatus(Blog::STATUS_PUBLISHED);
        $this->em->persist($draft);
        $this->em->persist($pub);
        $this->em->flush();

        // admin list — sees both
        $adminList = $this->authJson('GET', '/api/v1/admin/blogs?limit=50', $admin);
        $titles = array_column($adminList['data'], 'title');
        $this->assertContains("پیش‌نویس-$tag", $titles, 'admin list must show drafts');
        $this->assertContains("منتشر-$tag", $titles);

        // status filter
        $draftsOnly = $this->authJson('GET', '/api/v1/admin/blogs?limit=50&status=draft', $admin);
        $dTitles = array_column($draftsOnly['data'], 'title');
        $this->assertContains("پیش‌نویس-$tag", $dTitles);
        $this->assertNotContains("منتشر-$tag", $dTitles);

        // public list — drafts hidden
        $this->client->request('GET', '/api/v1/blogs?limit=50');
        $publicTitles = array_column(json_decode($this->client->getResponse()->getContent(), true)['data'], 'title');
        $this->assertNotContains("پیش‌نویس-$tag", $publicTitles, 'public list must never expose drafts');
    }

    public function testAdminListRequiresAdmin(): void
    {
        $user = $this->createUser(['ROLE_USER']);
        $this->authJson('GET', '/api/v1/admin/blogs', $user);
        $this->assertSame(403, $this->responseCode());
    }

    // ── Representative scope ──────────────────────────────────────────────────

    public function testRepresentativeCreatesPostInOwnCity(): void
    {
        $yasuj = $this->makeCity('یاسوج');
        $rep   = $this->makeRepresentation([$yasuj]);

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

    public function testRepresentativeCannotUseCityOutsideCoverage(): void
    {
        $yasuj  = $this->makeCity('یاسوج');
        $tabriz = $this->makeCity('تبریز');
        $rep    = $this->makeRepresentation([$yasuj]); // covers Yasuj only

        $this->authJson('POST', '/api/v1/representation/blog', $rep->getUser(), [
            'title'   => 'مقاله',
            'body'    => 'متن آزمایشی مقاله برای تست',
            'city_id' => $tabriz->getId(),
        ]);
        $this->assertSame(422, $this->responseCode(), 'city outside coverage must be rejected');
    }

    public function testRepresentativeListShowsOnlyOwnPosts(): void
    {
        $yasuj = $this->makeCity('یاسوج');
        $repA  = $this->makeRepresentation([$yasuj]);
        $repB  = $this->makeRepresentation([$yasuj]);
        $tag   = bin2hex(random_bytes(4));

        $this->authJson('POST', '/api/v1/representation/blog', $repA->getUser(), [
            'title' => "مالA-$tag", 'body' => 'متن آزمایشی مقاله برای تست', 'city_id' => $yasuj->getId(),
        ]);
        $this->authJson('POST', '/api/v1/representation/blog', $repB->getUser(), [
            'title' => "مالB-$tag", 'body' => 'متن آزمایشی مقاله برای تست', 'city_id' => $yasuj->getId(),
        ]);

        $list = $this->authJson('GET', '/api/v1/representation/blogs?limit=50', $repA->getUser());
        $titles = array_column($list['data'], 'title');
        $this->assertContains("مالA-$tag", $titles);
        $this->assertNotContains("مالB-$tag", $titles, "another rep's post leaked");
    }

    public function testRepresentativeCanGetOwnPostButNotOthers(): void
    {
        $yasuj = $this->makeCity('یاسوج');
        $repA  = $this->makeRepresentation([$yasuj]);
        $repB  = $this->makeRepresentation([$yasuj]);

        $created = $this->authJson('POST', '/api/v1/representation/blog', $repA->getUser(), [
            'title' => 'مقالهٔ A', 'body' => 'متن آزمایشی مقاله برای تست', 'city_id' => $yasuj->getId(),
        ]);
        $uuid = $created['data']['data']['uuid'];

        $own = $this->authJson('GET', "/api/v1/representation/blog/$uuid", $repA->getUser());
        $this->assertSame(200, $this->responseCode());
        $this->assertSame('مقالهٔ A', $own['data']['data']['title']);

        $this->authJson('GET', "/api/v1/representation/blog/$uuid", $repB->getUser());
        $this->assertSame(404, $this->responseCode());
    }

    public function testRepresentativeCannotEditOthersPost(): void
    {
        $yasuj = $this->makeCity('یاسوج');
        $repA  = $this->makeRepresentation([$yasuj]);
        $repB  = $this->makeRepresentation([$yasuj]);

        $created = $this->authJson('POST', '/api/v1/representation/blog', $repB->getUser(), [
            'title' => 'مقالهٔ B', 'body' => 'متن آزمایشی مقاله برای تست', 'city_id' => $yasuj->getId(),
        ]);
        $uuid = $created['data']['data']['uuid'];

        $this->authJson('PATCH', "/api/v1/representation/blog/$uuid", $repA->getUser(), ['title' => 'هک']);
        $this->assertSame(404, $this->responseCode(), "must not see another rep's post");
    }

    public function testNonRepresentativeIsForbidden(): void
    {
        $user = $this->createUser(['ROLE_USER']);
        $this->authJson('GET', '/api/v1/representation/blogs', $user);
        $this->assertSame(403, $this->responseCode());
    }

    // ── Scheduled publish ─────────────────────────────────────────────────────

    private function makeScheduled(?int $scheduledAt, ?string $reviewStatus): Blog
    {
        $blog = new Blog($this->createUser(['ROLE_ADMIN']), 'زمان‌بندی ' . bin2hex(random_bytes(3)), 'متن آزمایشی مقاله برای تست');
        $blog->setStatus(Blog::STATUS_DRAFT)->setScheduledAt($scheduledAt)->setReviewStatus($reviewStatus);
        $this->em->persist($blog);
        $this->em->flush();
        return $blog;
    }

    public function testScheduledPublisherPublishesDuePosts(): void
    {
        $publisher = static::getContainer()->get(ScheduledBlogPublisher::class);
        $past = time() - 60;

        $duePlain    = $this->makeScheduled($past, null);                    // manual, due -> publish
        $dueApproved = $this->makeScheduled($past, Blog::REVIEW_APPROVED);   // approved, due -> publish
        $duePending  = $this->makeScheduled($past, Blog::REVIEW_PENDING);    // due but unreviewed -> hold
        $future      = $this->makeScheduled(time() + 3600, null);            // not due -> hold

        $publisher->publishDue();
        $this->em->clear();

        $reload = fn(Blog $b) => $this->em->getRepository(Blog::class)->find($b->getId());
        $this->assertSame(Blog::STATUS_PUBLISHED, $reload($duePlain)->getStatus());
        $this->assertSame(Blog::STATUS_PUBLISHED, $reload($dueApproved)->getStatus());
        $this->assertSame(Blog::STATUS_DRAFT, $reload($duePending)->getStatus(), 'review gate wins over schedule');
        $this->assertSame(Blog::STATUS_DRAFT, $reload($future)->getStatus());
    }
}
