<?php

namespace App\Blog\Controller;

use App\Auth\Entity\User;
use App\Blog\Entity\Blog;
use App\Blog\Repository\BlogRepository;
use App\Blog\Service\BlogCacheInvalidator;
use App\Blog\Service\BlogWriter;
use App\Location\Entity\City;
use App\Location\Repository\CityRepository;
use App\Representation\Entity\Representation;
use App\Representation\Repository\RepresentationRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Security\Http\Attribute\IsGranted;

/**
 * Blog management scoped to a single representative. A representative sees and
 * edits only their own posts, and may only target cities within their coverage
 * (representation.cities). Admin CRUD lives in BlogController.
 */
#[OA\Tag(name: 'Blog')]
#[IsGranted('ROLE_REPRESENTATION')]
class RepresentationBlogController extends BaseController
{
    public function __construct(
        private readonly BlogRepository            $blogRepo,
        private readonly RepresentationRepository  $repRepo,
        private readonly CityRepository            $cityRepo,
        private readonly BlogWriter                $writer,
        private readonly BlogCacheInvalidator      $cacheInvalidator,
    ) {}

    private function currentRepresentation(User $user): Representation
    {
        $rep = $this->repRepo->findByUser($user);
        if ($rep === null) {
            throw new \App\Shared\Exception\AppException(ErrorCodes::ERR_AUTH_006, 'پروفایل نماینده یافت نشد', 403);
        }
        return $rep;
    }

    /** Resolve a city_id, enforcing it belongs to the representative's coverage. */
    private function resolveOwnedCity(mixed $cityId, Representation $rep): ?City
    {
        if ($cityId === null || $cityId === '' || (int) $cityId === 0) {
            return null; // nationwide is not allowed here, but null is handled by the caller
        }
        $city = $this->cityRepo->find((int) $cityId);
        if ($city === null) {
            throw new \App\Shared\Exception\AppException(ErrorCodes::ERR_VALIDATION_002, 'شهر یافت نشد', 422);
        }
        if (!in_array((int) $city->getId(), $rep->cityIds(), true)) {
            throw new \App\Shared\Exception\AppException(ErrorCodes::ERR_VALIDATION_002, 'این شهر جزو حوزهٔ نمایندگی شما نیست', 422);
        }
        return $city;
    }

    private function ownedBlogOr404(string $uuid, Representation $rep): Blog
    {
        $blog = $this->blogRepo->findByUuid($uuid);
        if ($blog === null || $blog->getRepresentation()?->getId() !== $rep->getId()) {
            throw new \App\Shared\Exception\AppException(ErrorCodes::ERR_NOT_FOUND_001, 'مقاله یافت نشد', 404);
        }
        return $blog;
    }

    #[OA\Get(path: '/api/v1/representation/blogs', summary: "List the representative's own blog posts", security: [['bearerAuth' => []]])]
    #[Route('/api/v1/representation/blogs', methods: ['GET'])]
    public function list(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $rep    = $this->currentRepresentation($user);
        $page   = max(1, (int) $request->query->get('page', 1));
        $limit  = min(50, max(1, (int) $request->query->get('limit', 20)));
        $status = $request->query->get('status') ?: null;

        $items = array_map(
            fn(Blog $b) => $b->toListArray(),
            $this->blogRepo->findByRepresentation($rep, $page, $limit, $status)
        );
        $total = $this->blogRepo->countByRepresentation($rep, $status);

        return $this->paginated($items, $total, $page, $limit);
    }

    #[OA\Get(path: '/api/v1/representation/blog/{uuid}', summary: "Get one of the representative's own posts", security: [['bearerAuth' => []]])]
    #[Route('/api/v1/representation/blog/{uuid}', methods: ['GET'])]
    public function detail(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        $rep  = $this->currentRepresentation($user);
        $blog = $this->ownedBlogOr404($uuid, $rep);
        return $this->success(['data' => $blog->toArray()]);
    }

    #[OA\Post(path: '/api/v1/representation/blog', summary: 'Create a post owned by the representative', security: [['bearerAuth' => []]])]
    #[Route('/api/v1/representation/blog', methods: ['POST'])]
    public function create(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $rep  = $this->currentRepresentation($user);
        $data = json_decode($request->getContent(), true) ?? [];

        $title = trim((string) ($data['title'] ?? ''));
        $body  = trim((string) ($data['body'] ?? ''));
        if ($title === '' || $body === '') {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'title و body الزامی است', 422);
        }

        $blog = new Blog($user, $title, $body);
        $blog->setRepresentation($rep);
        $blog->setCity($this->resolveOwnedCity($data['city_id'] ?? null, $rep));
        if (!empty($data['summary']))   $blog->setSummary($data['summary']);
        if (!empty($data['tags']))      $blog->setTags((array) $data['tags']);
        if (!empty($data['image_url'])) $blog->setImageUrl($data['image_url']);
        if (!empty($data['status']) && in_array($data['status'], [Blog::STATUS_DRAFT, Blog::STATUS_PUBLISHED], true)) {
            $blog->setStatus($data['status']);
        }
        $this->writer->applySeoFields($blog, $data);

        if ($this->blogRepo->findBySlug($blog->getSlug()) !== null) {
            $blog->setSlug($blog->getSlug() . '-' . substr(uniqid(), -4));
        }
        $this->blogRepo->save($blog);
        $this->cacheInvalidator->invalidate($blog);

        return $this->success(['data' => $blog->toArray()], 201);
    }

    #[OA\Patch(path: '/api/v1/representation/blog/{uuid}', summary: "Update the representative's own post", security: [['bearerAuth' => []]])]
    #[Route('/api/v1/representation/blog/{uuid}', methods: ['PATCH'])]
    public function update(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $rep  = $this->currentRepresentation($user);
        $blog = $this->ownedBlogOr404($uuid, $rep);
        $data = json_decode($request->getContent(), true) ?? [];

        if (array_key_exists('title', $data))   $blog->setTitle((string) $data['title']);
        if (array_key_exists('body', $data))    $blog->setBody((string) $data['body']);
        if (array_key_exists('summary', $data)) $blog->setSummary($data['summary']);
        if (array_key_exists('tags', $data))    $blog->setTags((array) $data['tags']);
        if (array_key_exists('image_url', $data)) $blog->setImageUrl($data['image_url'] ?: null);
        if (array_key_exists('status', $data) && in_array($data['status'], [Blog::STATUS_DRAFT, Blog::STATUS_PUBLISHED, Blog::STATUS_ARCHIVED], true)) {
            $blog->setStatus($data['status']);
        }
        if (array_key_exists('city_id', $data)) {
            $blog->setCity($this->resolveOwnedCity($data['city_id'], $rep));
        }
        $this->writer->applySeoFields($blog, $data);
        $this->blogRepo->save($blog);
        $this->cacheInvalidator->invalidate($blog);

        return $this->success(['data' => $blog->toArray()]);
    }

    #[OA\Delete(path: '/api/v1/representation/blog/{uuid}', summary: "Delete the representative's own post", security: [['bearerAuth' => []]])]
    #[Route('/api/v1/representation/blog/{uuid}', methods: ['DELETE'])]
    public function delete(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        $rep  = $this->currentRepresentation($user);
        $blog = $this->ownedBlogOr404($uuid, $rep);
        $this->cacheInvalidator->invalidate($blog);
        $this->blogRepo->remove($blog);

        return $this->success(['message' => 'مقاله حذف شد']);
    }
}
