<?php
declare(strict_types=1);

namespace Opentalent\OtAdmin\Http;


use Doctrine\DBAL\Driver\Exception;
use Opentalent\OtAdmin\Controller\SiteController;
use Opentalent\OtCore\Exception\InvalidWebsiteConfigurationException;
use Opentalent\OtCore\Exception\NoSuchOrganizationException;
use Opentalent\OtCore\Exception\NoSuchRecordException;
use Opentalent\OtCore\Exception\NoSuchWebsiteException;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Core\Http\JsonResponse;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
 * Actions for Http API calls
 *
 * @package Opentalent\OtAdmin\Http
 */
class ApiController implements LoggerAwareInterface
{
    use LoggerAwareTrait;

    const array ALLOWED_IPS = [
        '/^127\.0\.0\.[0-1]$/', // Localhost
        '/^localhost$/',  // Localhost
        '/^10\.8\.0\.\d{1,3}$/', // 10.8.0.[0-255] - VPN
        '/^141\.94\.117\.((3[3-9])|(4\d)|(5\d)|(6[0-1]))$/',   // 141.94.117.[33-61] - Opentalent hosts public ips
        '/^172\.16\.0.\d{1,3}$/',   // 172.16.0.[0-255] - Opentalent hosts private ips
        '/^172\.20\.\d{1,3}\.\d{1,3}$/',  // 172.20.[0-255].[0-255] - Docker
    ];

    private readonly SiteController $siteController;

    public function __construct() {
        $this->siteController = GeneralUtility::makeInstance(SiteController::class);
    }

    /**
     * Returns true if the client Ip is allowed
     *
     * @param string $clientIp
     * @return bool
     */
    public static function isIpAllowed(string $clientIp): bool
    {
        foreach (self::ALLOWED_IPS as $ipRule) {
            if (preg_match($ipRule, $clientIp)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check that the client Ip is allowed, else throw a Runtime error
     *
     * @return bool
     */
    private function assertIpAllowed(): bool
    {
        $clientIp = $_SERVER['REMOTE_ADDR'];
        if (!self::isIpAllowed($clientIp)){
            $route = $_REQUEST['route'];
            $this->logger->error(sprintf(
                "OtAdmin API: an attempt was made to call the route " .
                $route . " from an non-allowed IP (" . $clientIp . ")"));
            throw new \RuntimeException("Not allowed");
        }
        return true;
    }

    /**
     * Retrieve the organization's id from the given request parameters
     *
     * @param ServerRequest $request
     * @return int
     */
    private function getOrganizationId(ServerRequest $request): int
    {
        $params = $request->getQueryParams();
        $organizationId = $params['organization-id'];
        if (!$organizationId) {
            throw new \RuntimeException("Missing parameter: 'organization-id'");
        }
        return (int)$organizationId;
    }

    /**
     * -- Target of the route 'site_infos' --
     *
     * Return the main information about the organization's website
     *
     * @param ServerRequest $request
     * @return JsonResponse
     * @throws \Exception
     */
    public function getSiteInfosAction(
        ServerRequest $request,
        SiteController $siteController
    ): JsonResponse
    {
        $this->assertIpAllowed();

        $organizationId = $this->getOrganizationId($request);


        $infos = $siteController->getSiteInfosAction($organizationId);

        return new JsonResponse($infos);
    }

    /**
     * -- Target of the route 'site_create' --
     * >> Requires a query param named 'organization-id' (int)
     *
     * Create the organization's website
     *
     * @param ServerRequest $request
     * @return JsonResponse
     * @throws \Exception
     */
    public function createSiteAction(ServerRequest $request): JsonResponse
    {
        $this->assertIpAllowed();

        $organizationId = $this->getOrganizationId($request);

        $rootUid = $this->siteController->createSiteAction($organizationId);

        $this->logger->info(sprintf(
            "OtAdmin API: A new website has been created with root page uid=" . $rootUid .
            " for the organization " . $organizationId));

        return new JsonResponse(
            [
                'organization_id' => $organizationId,
                'msg' => "A new website has been created with root page uid=" . $rootUid,
                'root_uid' => $rootUid
            ]
        );
    }

    /**
     * -- Target of the route 'site_update' --
     * >> Requires a query param named 'organization-id' (int)
     *
     * Update the settings of the organization's website
     *
     * @param ServerRequest $request
     * @return JsonResponse
     * @throws \Exception
     */
    public function updateSiteConstantsAction(ServerRequest $request): JsonResponse
    {
        $this->assertIpAllowed();

        $organizationId = $this->getOrganizationId($request);
        $deep = (isset($queryParams['deep']) && $queryParams['deep']);

        $rootUid = $this->siteController->updateSiteAction($organizationId, $deep);

        $this->logger->info(sprintf(
            "OtAdmin API: The website with root uid " . $rootUid . " has been updated " .
            " (organization: " . $organizationId . ")"));

        return new JsonResponse(
            [
                'organization_id' => $organizationId,
                'msg' => "The website with root uid " . $rootUid . " has been updated",
                'root_uid' => $rootUid
            ]
        );
    }

    /**
     * -- Target of the route 'redirect_add' --
     * >> Requires query params named 'from-domain' (string) and 'to-domain' (string)
     *
     * Add or update a redirection from 'from-domain' to 'to-domain'
     *
     * @param ServerRequest $request
     * @return JsonResponse
     * @throws \Exception
     */
    public function addRedirectionAction(ServerRequest $request): JsonResponse
    {
        $this->assertIpAllowed();

        $fromDomain = (isset($queryParams['from-domain']) && $queryParams['from-domain']);
        $toDomain = (isset($queryParams['to-domain']) && $queryParams['to-domain']);

        $res = $this->siteController->addRedirection($fromDomain, $toDomain);

        if ($res === SiteController::REDIRECTION_UPDATED) {
            $msg = "An existing redirection has been updated ";
        } elseif ($res === SiteController::REDIRECTION_CREATED) {
            $msg = "A redirection has been added ";
        }

        $this->logger->info(sprintf(
            "OtAdmin API: " . $msg . " from " . $fromDomain . " to " . $toDomain
        ));
        return new JsonResponse(
            [
                'msg' => $msg . " from " . $fromDomain . " to " . $toDomain,
            ]
        );
    }

    /**
     * -- Target of the route 'site_delete' --
     * >> Requires a query param named 'organization-id' (int)
     *
     * Proceeds to a soft-deletion of the organization's website
     *
     * @param ServerRequest $request
     * @return JsonResponse
     * @throws \Exception
     */
    public function deleteSiteAction(ServerRequest $request): JsonResponse
    {
        $this->assertIpAllowed();

        $organizationId = $this->getOrganizationId($request);

        $params = $request->getQueryParams();

        $rootUid = $this->siteController->deleteSiteAction($organizationId);

        $this->logger->info(sprintf(
            "OtAdmin API: The website with root uid " . $rootUid . " has been soft-deleted " .
            " (organization: " . $organizationId . ")"));

        return new JsonResponse(
            [
                'organization_id' => $organizationId,
                'msg' => "The website with root uid " . $rootUid . " has been soft-deleted. Use the /site/undelete route to restore it.",
                'root_uid' => $rootUid
            ]
        );
    }

    /**
     * -- Target of the route 'site_undelete' --
     * >> Requires a query param named 'organization-id' (int)
     *
     * Restore a soft-deleted organization's website
     *
     * @param ServerRequest $request
     * @return JsonResponse
     * @throws \Exception
     */
    public function undeleteSiteAction(ServerRequest $request): JsonResponse
    {
        $this->assertIpAllowed();

        $organizationId = $this->getOrganizationId($request);

        $rootUid = $this->siteController->undeleteSiteAction($organizationId);

        $this->logger->info(sprintf(
            "OtAdmin API: The website with root uid " . $rootUid . " has been restored " .
            " (organization: " . $organizationId . ")"));

        return new JsonResponse(
            [
                'organization_id' => $organizationId,
                'msg' => "The website with root uid " . $rootUid . " has been restored",
                'root_uid' => $rootUid
            ]
        );
    }

    /**
     * -- Target of the route 'site_clearcache' --
     * >> Requires a query param named 'organization-id' (int)
     *
     * Clear the cache of the organization's website
     *
     * @param ServerRequest $request
     * @return JsonResponse
     * @throws \Exception
     */
    public function clearSiteCacheAction(ServerRequest $request): JsonResponse
    {
        $this->assertIpAllowed();

        $organizationId = $this->getOrganizationId($request);

        $queryParams = $request->getQueryParams();
        $clearAll = (isset($queryParams['all']) && $queryParams['all']);;

        $rootUid = $this->siteController->clearSiteCacheAction($organizationId, $clearAll);

        return new JsonResponse(
            [
                'organization_id' => $organizationId,
                'msg' => "The cache has been cleared for the website with root uid " . $rootUid . "",
                'root_uid' => $rootUid
            ]
        );
    }

    /**
     * -- Target of the route 'site_setdomain' --
     * >> Requires a query param named 'organization-id' (int)
     *    and a parameter named 'domain' (string)
     *
     * Set a new domain for the organization website
     *
     * @param ServerRequest $request
     * @return JsonResponse
     * @throws \Exception
     */
    public function setSiteCustomDomainAction(ServerRequest $request): JsonResponse
    {
        $this->assertIpAllowed();

        $organizationId = $this->getOrganizationId($request);

        $queryParams = $request->getQueryParams();
        $domain = $queryParams['domain'];
        if (!$domain) {
            throw new \RuntimeException("Missing 'domain' parameter");
        }
        $redirect = (isset($queryParams['redirect']) && $queryParams['redirect']);

        $rootUid = $this->siteController->setSiteCustomDomainAction($organizationId, $domain, $redirect);

        return new JsonResponse(
            [
                'organization_id' => $organizationId,
                'msg' => "The cache has been cleared for the website with root uid " . $rootUid . "",
                'root_uid' => $rootUid
            ]
        );
    }

    /**
     * -- Target of the route 'site_resetperms' --
     * >> Requires a query param named 'organization-id' (int)
     *
     * Reset the permissions of the website be users (admin, editors...)
     *
     * @param ServerRequest $request
     * @return JsonResponse
     * @throws \Exception
     */
    public function resetBeUserPermsAction(ServerRequest $request): JsonResponse
    {
        $this->assertIpAllowed();

        $organizationId = $this->getOrganizationId($request);

        $rootUid = $this->siteController->resetBeUserPermsAction($organizationId);

        return new JsonResponse(
            [
                'organization_id' => $organizationId,
                'msg' => "The website with root uid " . $rootUid . " had its be users permissions reset",
                'root_uid' => $rootUid
            ]
        );
    }

    /**
     * -- Target of the route 'site_status' --
     * >> Requires a query param named 'organization-id' (int)
     *
     * Returns the current status of the website
     *
     * @param ServerRequest $request
     * @param SiteController $siteController
     * @return JsonResponse
     * @throws Exception
     * @throws InvalidWebsiteConfigurationException
     * @throws NoSuchOrganizationException
     * @throws NoSuchRecordException
     * @throws NoSuchWebsiteException
     */
    public function getSiteStatusAction(
        ServerRequest $request
    ): JsonResponse
    {
        $this->assertIpAllowed();

        $organizationId = $this->getOrganizationId($request);

        $queryParams = $request->getQueryParams();
        $full = (isset($queryParams['full']) && $queryParams['full']);

        $status = $this->siteController->getSiteStatusAction($organizationId, $full);

        return new JsonResponse($status->toArray());
    }

    /**
     * -- Target of the route 'scan' --
     *
     * Scan the whole Typo3 database and return the results
     *
     * @param ServerRequest $request
     * @return JsonResponse
     * @throws \Exception
     */
    public function scanAllAction(ServerRequest $request): JsonResponse
    {
        $this->assertIpAllowed();

        $queryParams = $request->getQueryParams();
        $full = (isset($queryParams['full']) && $queryParams['full']);
        $results = $this->siteController->scanAllAction($full);

        return new JsonResponse($results);
    }
}
