<?php

declare(strict_types=1);

namespace BikeShare\Controller\Api;

use BikeShare\Db\DbInterface;
use BikeShare\Enum\Action;
use BikeShare\Rent\RentSystemFactory;
use BikeShare\Repository\BikeRepository;
use BikeShare\Repository\HistoryRepository;
use BikeShare\Repository\NoteRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BikeController extends AbstractController
{
    public function __construct(private readonly BikeRepository $bikeRepository)
    {
    }

    #[Route('/api/bike', name: 'api_bike_index', methods: ['GET'])]
    public function index(): Response
    {
        $this->denyAccessUnlessGranted('ROLE_ADMIN');

        $bikes = $this->bikeRepository->findAll();

        return $this->json($bikes);
    }

    #[Route('/api/bike/{bikeNumber}', name: 'api_bike_item', requirements: ['bikeNumber' => '\d+'], methods: ['GET'])]
    public function item(
        $bikeNumber
    ): Response {
        $this->denyAccessUnlessGranted('ROLE_ADMIN');

        if (empty($bikeNumber) || !is_numeric($bikeNumber)) {
            return $this->json([], Response::HTTP_BAD_REQUEST);
        }

        $bikes = [$this->bikeRepository->findItem((int)$bikeNumber)];

        return $this->json($bikes);
    }

    #[Route(
        path: '/api/bike/{bikeNumber}/lastUsage',
        name: 'api_bike_last_usage',
        requirements: ['bikeNumber' => '\d+'],
        methods: ['GET'],
    )]
    public function lastUsage(
        $bikeNumber
    ): Response {
        $this->denyAccessUnlessGranted('ROLE_ADMIN');

        if (empty($bikeNumber) || !is_numeric($bikeNumber)) {
            return $this->json([], Response::HTTP_BAD_REQUEST);
        }

        $bikes = $this->bikeRepository->findItemLastUsage((int)$bikeNumber);

        return $this->json($bikes);
    }

    #[Route(
        path: '/api/bike/{bikeNumber}/rent',
        name: 'api_bike_rent',
        requirements: ['bikeNumber' => '\d+'],
        methods: ['PUT'],
    )]
    public function rentBike(
        $bikeNumber,
        RentSystemFactory $rentSystemFactory
    ): Response {
        $this->denyAccessUnlessGranted('ROLE_USER');

        if (empty($bikeNumber) || !is_numeric($bikeNumber)) {
            return $this->json([], Response::HTTP_BAD_REQUEST);
        }

        $response = $rentSystemFactory->getRentSystem('web')->rentBike(
            $this->getUser()->getUserId(),
            (int)$bikeNumber
        );

        return $this->json($response);
    }

    #[Route(
        path: '/api/bike/{bikeNumber}/return',
        name: 'api_bike_return',
        requirements: ['standName' => '\w+', 'bikeNumber' => '\d+'],
        methods: ['PUT'],
    )]
    public function returnBike(
        $bikeNumber,
        $standName,
        Request $request,
        RentSystemFactory $rentSystemFactory
    ): Response {
        $this->denyAccessUnlessGranted('ROLE_USER');

        if (empty($bikeNumber) || !is_numeric($bikeNumber)) {
            return $this->json([], Response::HTTP_BAD_REQUEST);
        }

        $response = $rentSystemFactory->getRentSystem('web')->returnBike(
            $this->getUser()->getUserId(),
            (int)$bikeNumber,
            $standName,
            $request->request->get('note', ''),
        );

        return $this->json($response);
    }

    #[Route(
        path: '/api/bike/{bikeNumber}/forceRent',
        name: 'api_bike_force_rent',
        requirements: ['bikeNumber' => '\d+'],
        methods: ['PUT'],
    )]
    public function forceRentBike(
        $bikeNumber,
        RentSystemFactory $rentSystemFactory
    ): Response {
        $this->denyAccessUnlessGranted('ROLE_ADMIN');

        if (empty($bikeNumber) || !is_numeric($bikeNumber)) {
            return $this->json([], Response::HTTP_BAD_REQUEST);
        }

        $response = $rentSystemFactory->getRentSystem('web')->rentBike(
            $this->getUser()->getUserId(),
            (int)$bikeNumber,
            true // Force rent
        );

        return $this->json($response);
    }

    #[Route(
        path: '/api/bike/{bikeNumber}/forceReturn',
        name: 'api_bike_force_return',
        requirements: ['standName' => '\w+', 'bikeNumber' => '\d+'],
        methods: ['PUT'],
    )]
    public function forceReturnBike(
        $bikeNumber,
        $standName,
        Request $request,
        RentSystemFactory $rentSystemFactory
    ): Response {
        $this->denyAccessUnlessGranted('ROLE_ADMIN');

        if (empty($bikeNumber) || !is_numeric($bikeNumber)) {
            return $this->json([], Response::HTTP_BAD_REQUEST);
        }

        $response = $rentSystemFactory->getRentSystem('web')->returnBike(
            $this->getUser()->getUserId(),
            (int)$bikeNumber,
            $standName,
            $request->request->get('note', ''),
            true // Force return
        );

        return $this->json($response);
    }

    #[Route(
        path: '/api/bike/{bikeNumber}/code',
        name: 'api_bike_set_code',
        requirements: ['bikeNumber' => '\d+'],
        methods: ['PUT'],
    )]
    public function setCode(
        $bikeNumber,
        Request $request,
        HistoryRepository $historyRepository,
    ): Response {
        $this->denyAccessUnlessGranted('ROLE_ADMIN');

        if (empty($bikeNumber) || !is_numeric($bikeNumber)) {
            return $this->json([], Response::HTTP_BAD_REQUEST);
        }

        $code = $request->request->get('code');
        if (!is_string($code) || !preg_match('/^\d{4}$/', $code)) {
            return $this->json([
                'message' => 'Invalid code format. Use four digits.',
                'error' => 1,
            ], Response::HTTP_BAD_REQUEST);
        }

        $bikeNumber = (int)$bikeNumber;
        $formattedCode = sprintf('%04d', (int)$code);

        $this->bikeRepository->updateBikeCode($bikeNumber, (int)$code);

        $historyRepository->addItem(
            $this->getUser()->getUserId(),
            $bikeNumber,
            Action::CHANGE_CODE,
            $formattedCode,
        );

        $response = [
            'message' => sprintf('Bike %d code updated to %s.', $bikeNumber, $formattedCode),
            'error' => 0,
            'bikeNumber' => $bikeNumber,
            'code' => $formattedCode,
        ];

        return $this->json($response);
    }

    #[Route(
        path: '/api/bike/{bikeNumber}/revert',
        name: 'api_bike_revert',
        requirements: ['bikeNumber' => '\d+'],
        methods: ['PUT'],
    )]
    public function revertBike(
        $bikeNumber,
        RentSystemFactory $rentSystemFactory
    ): Response {
        $this->denyAccessUnlessGranted('ROLE_ADMIN');

        if (empty($bikeNumber) || !is_numeric($bikeNumber)) {
            return $this->json([], Response::HTTP_BAD_REQUEST);
        }

        $response = $rentSystemFactory->getRentSystem('web')->revertBike(
            $this->getUser()->getUserId(),
            (int)$bikeNumber,
        );

        return $this->json($response);
    }

    #[Route(
        path: '/api/bike/{bikeNumber}/removeNote',
        name: 'api_bike_remove_note',
        requirements: ['bikeNumber' => '\d+'],
        methods: ['DELETE'],
    )]
    public function removeNote(
        $bikeNumber,
        NoteRepository $noteRepository,
        Request $request
    ): Response {
        $this->denyAccessUnlessGranted('ROLE_ADMIN');

        if (empty($bikeNumber) || !is_numeric($bikeNumber)) {
            return $this->json([], Response::HTTP_BAD_REQUEST);
        }

        $pattern = $request->request->get('pattern');

        $deletedNotesCount = $noteRepository->deleteBikeNote((int)$bikeNumber, $pattern);

        $response = [
            'message' => $deletedNotesCount . ' note(s) removed successfully',
            'error' => 0,
        ];

        return $this->json($response);
    }

    #[Route(
        path: '/api/bike/{bikeNumber}/trip',
        name: 'api_bike_trip',
        requirements: ['bikeNumber' => '\d+'],
        methods: ['GET'],
    )]
    public function bikeTrip(
        $bikeNumber,
        HistoryRepository $historyRepository
    ): Response {
        $this->denyAccessUnlessGranted('ROLE_ADMIN');

        if (empty($bikeNumber) || !is_numeric($bikeNumber)) {
            return $this->json([], Response::HTTP_BAD_REQUEST);
        }

        $userTrip = $historyRepository->findBikeTrip(
            (int)$bikeNumber,
            new \DateTimeImmutable('-1 month')
        );

        return $this->json($userTrip);
    }

    /**
     * For using this endpoint, bikes should have geolocation devices. Perhaps it should be some authorization for it
     */
    #[Route(
        path: '/api/bike/{bikeNumber}/geoLocation',
        name: 'api_bike_geo_location',
        requirements: ['bikeNumber' => '\d+'],
        methods: ['POST'],
    )]
    public function bikeGeoLocation(
        $bikeNumber,
        Request $request,
        DbInterface $db,
        ClockInterface $clock,
    ): Response {
        //Currently does not work
        return $this->json([]);

        $this->denyAccessUnlessGranted('PUBLIC_ACCESS');

        if (
            !$request->request->has('lat')
            || !$request->request->has('long')
            || !is_numeric($request->request->get('lat'))
            || !is_numeric($request->request->get('long'))
        ) {
            return $this->json([], Response::HTTP_BAD_REQUEST);
        }

        $db->query(
            "INSERT INTO geolocation 
                SET bikeNum = :bikeNumber, latitude = :latitude, longitude= :longitude, time = :time",
            [
                'bikeNumber' => $bikeNumber,
                'latitude' => (float)$request->request->get('lat'),
                'longitude' => (float)$request->request->get('long'),
                'time' => $clock->now()->format('Y-m-d H:i:s'),
            ]
        );

        return $this->json([]);
    }
}
