<?php

namespace App\Controller;

use App\Entity\Product;
use App\Form\UpdateProductType;
use App\Repository\ProductRepository;
use App\Representation\PaginatedProducts;
use Doctrine\ORM\EntityManagerInterface;
use JMS\Serializer\Annotation as Serializer;
use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Validator\Validator\ValidatorInterface;

use Swagger\Annotations as SWG;
use Nelmio\ApiDocBundle\Annotation\Model;

/**
 * @Route("/api")
 */
class ProductsController extends Controller
{
    private $serializer;

    public function __construct(SerializerInterface $serializer)
    {
        $this->serializer = $serializer;
    }

    /**
     * Paginated list of products for sale
     *
     * @Route("/products", name="productsList", defaults={"page": 1})
     * @Route("/products/page/{page}", name="productsListPaginated")
     * @Method("GET")
     *
     * @SWG\Response(
     *     response=200,
     *     description="Returns the list of the products for sale",
     *     @SWG\Schema(
     *         type="array",
     *         @Model(type=Product::class, groups={"list"})
     *     )
     * )
     */
    public function listProducts(ProductRepository $repository, Request $request, $page)
    {
        $search = $request->query->get('search');
        $filteredProducts = $repository->getFilteredProducts($search, $page);

        $productsRepresentation = new PaginatedProducts($filteredProducts, $page);

        return $this->getJsonReponse($productsRepresentation);
    }

    /**
     * Product creation
     *
     * @Route("/products", name="createProduct")
     * @Method("POST")
     *
     * @SWG\Response(
     *     response=201,
     *     description="Returns the inserted product",
     *     @Model(type=Product::class, groups={"detail"})
     * )
     * @SWG\Parameter(
     *     name="body",
     *     in="body",
     *     type="json",
     *     description="The product to create",
     *     @SWG\Schema(
     *        type="object",
     *        @SWG\Property(
     *             type="string",
     *             property="name",
     *             example="Playstation 4"
     *        ),
     *        @SWG\Property(
     *             type="string",
     *             property="description",
     *             example="An awesome gaming console"
     *        ),
     *        @SWG\Property(
     *             type="decimal",
     *             property="price",
     *             example=349.99
     *        ),
     *     )
     * )
     */
    public function create(Request $request, EntityManagerInterface $em, ValidatorInterface $validator)
    {
        $product = $this->getUnserializedProduct($request);

        $errors = $validator->validate($product);
        if (count($errors)) {
            return $this->getJsonReponse($errors, Response::HTTP_BAD_REQUEST);
        }

        $em->persist($product);
        $em->flush();

        return $this->getJsonReponse(
            $product,
            Response::HTTP_CREATED,
            ['Location' => '/products/'.$product->getId()]
        );
    }

    /**
     * Get product details
     *
     * @Route("/products/{id}", name="productDetail")
     * @Method("GET")
     *
     * @SWG\Response(
     *     response=200,
     *     description="Returns the detail of a product",
     *     @Model(type=Product::class, groups={"detail"})
     * )
     */
    public function show(Product $product)
    {
        return $this->getJsonReponse($product);
    }

    /**
     * Update of a product
     *
     * @Route("/products/{id}", name="productUpdate")
     * @Method({"PUT", "PATCH"})
     */
    public function update(Product $product, Request $request, EntityManagerInterface $em)
    {
        $form = $this->createForm(UpdateProductType::class, $product);
        $this->processForm($request, $form);

        $em = $this->getDoctrine()->getManager();

        $em->persist($product);
        $em->flush();

        return $this->getJsonReponse($product);
    }

    /**
     * Product removal
     *
     * @Route("/products/{id}", name="productDelete")
     * @Method("DELETE")
     */
    public function delete(Product $product, EntityManagerInterface $em)
    {
        $em->remove($product);
        $em->flush();

        return $this->getJsonReponse(null, Response::HTTP_NO_CONTENT);
    }

    /**
     * @param Request $request
     * @param SerializerInterface $serializer
     * @return array|\JMS\Serializer\scalar|object
     */
    private function getUnserializedProduct(Request $request)
    {
        $product = $this->serializer->deserialize(
            $request->getContent(),
            Product::class,
            'json'
        );
        return $product;
    }

    private function getJsonReponse($data = null, int $status = 200, array $headers = []): JsonResponse
    {
        $serializedData = $data;
        if (!is_null($data)) {
            $serializedData = $this->serializer->serialize(
                $data,
                'json',
                SerializationContext::create()->setVersion($this->getVersionToServe())
            );
        }
        $response = new JsonResponse(null, $status, $headers);
        $response->setContent($serializedData);

        return $response;

    }

    private function getVersionToServe()
    {
        $request = $this->container->get('request_stack')->getCurrentRequest();
        $acceptHeader = $request->headers->get('Accept');
        $version = "1.0";
        if (!empty($acceptHeader)) {
            $acceptParts = preg_split("/;/", $acceptHeader);
            foreach ($acceptParts as $part) {
                if (substr($part, 0, 8) != "version=") continue;
                $version = substr($part, 8, strlen($part) -1);
            }
        }
        return $version;
    }
}
