<?php

/**
 * @file api/v1/institutions/PKPInstitutionHandler.php
 *
 * Copyright (c) 2022 Simon Fraser University
 * Copyright (c) 2022 John Willinsky
 * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
 *
 * @class PKPInstitutionHandler
 *
 * @ingroup api_v1_institutions
 *
 * @brief Handle API requests for institution operations.
 *
 */

namespace PKP\API\v1\institutions;

use APP\facades\Repo;
use PKP\core\APIResponse;
use PKP\handler\APIHandler;
use PKP\plugins\Hook;
use PKP\security\authorization\ContextRequiredPolicy;
use PKP\security\authorization\PolicySet;
use PKP\security\authorization\RoleBasedHandlerOperationPolicy;
use PKP\security\authorization\UserRolesRequiredPolicy;
use PKP\security\Role;
use PKP\services\PKPSchemaService;
use Slim\Http\Request as SlimHttpRequest;

class PKPInstitutionHandler extends APIHandler
{
    /** @var int The default number of institutions to return in one request */
    public const DEFAULT_COUNT = 30;

    /** @var int The maximum number of institutions to return in one request */
    public const MAX_COUNT = 100;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->_handlerPath = 'institutions';
        $this->_endpoints = [
            'GET' => [
                [
                    'pattern' => $this->getEndpointPattern(),
                    'handler' => [$this, 'getMany'],
                    'roles' => [Role::ROLE_ID_MANAGER],
                ],
                [
                    'pattern' => $this->getEndpointPattern() . '/{institutionId:\d+}',
                    'handler' => [$this, 'get'],
                    'roles' => [Role::ROLE_ID_MANAGER],
                ],
            ],
            'POST' => [
                [
                    'pattern' => $this->getEndpointPattern(),
                    'handler' => [$this, 'add'],
                    'roles' => [Role::ROLE_ID_MANAGER],
                ],
            ],
            'PUT' => [
                [
                    'pattern' => $this->getEndpointPattern() . '/{institutionId:\d+}',
                    'handler' => [$this, 'edit'],
                    'roles' => [Role::ROLE_ID_MANAGER],
                ],
            ],
            'DELETE' => [
                [
                    'pattern' => $this->getEndpointPattern() . '/{institutionId:\d+}',
                    'handler' => [$this, 'delete'],
                    'roles' => [Role::ROLE_ID_MANAGER],
                ],
            ],
        ];
        parent::__construct();
    }

    /**
     * @copydoc PKPHandler::authorize
     */
    public function authorize($request, &$args, $roleAssignments)
    {
        $this->addPolicy(new UserRolesRequiredPolicy($request), true);

        $rolePolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);

        $this->addPolicy(new ContextRequiredPolicy($request));

        foreach ($roleAssignments as $role => $operations) {
            $rolePolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, $role, $operations));
        }
        $this->addPolicy($rolePolicy);

        return parent::authorize($request, $args, $roleAssignments);
    }

    /**
     * Get a single institution
     */
    public function get(SlimHttpRequest $slimRequest, APIResponse $response, array $args): APIResponse
    {
        if (!Repo::institution()->exists((int) $args['institutionId'], $this->getRequest()->getContext()->getId())) {
            return $response->withStatus(404)->withJsonError('api.institutions.404.institutionNotFound');
        }
        $institution = Repo::institution()->get((int) $args['institutionId']);
        return $response->withJson(Repo::institution()->getSchemaMap()->map($institution), 200);
    }

    /**
     * Get a collection of institutions
     */
    public function getMany(SlimHttpRequest $slimRequest, APIResponse $response, array $args): APIResponse
    {
        $collector = Repo::institution()->getCollector()
            ->limit(self::DEFAULT_COUNT)
            ->offset(0);

        foreach ($slimRequest->getQueryParams() as $param => $val) {
            switch ($param) {
                case 'count':
                    $collector->limit(min((int) $val, self::MAX_COUNT));
                    break;
                case 'offset':
                    $collector->offset((int) $val);
                    break;
                case 'searchPhrase':
                    $collector->searchPhrase($val);
                    break;
            }
        }

        $collector->filterByContextIds([$this->getRequest()->getContext()->getId()]);

        Hook::call('API::institutions::params', [$collector, $slimRequest]);

        $institutions = $collector->getMany();

        return $response->withJson([
            'itemsMax' => $collector->getCount(),
            'items' => Repo::institution()->getSchemaMap()->summarizeMany($institutions->values())->values(),
        ], 200);
    }

    /**
     * Add an institution
     *
     * @throws \Exception For sending a request to the API endpoint of a particular context.
     */
    public function add(SlimHttpRequest $slimRequest, APIResponse $response, array $args): APIResponse
    {
        $request = $this->getRequest();

        $params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_INSTITUTION, $slimRequest->getParsedBody());
        $params['contextId'] = $request->getContext()->getId();
        // Convert IP ranges string to array
        if (!empty($params['ipRanges'])) {
            $params['ipRanges'] = $this->convertIpToArray($params['ipRanges']);
        }

        $primaryLocale = $request->getContext()->getPrimaryLocale();
        $allowedLocales = $request->getContext()->getSupportedFormLocales();
        $errors = Repo::institution()->validate(null, $params, $allowedLocales, $primaryLocale);
        if (!empty($errors)) {
            return $response->withStatus(400)->withJson($errors);
        }

        $institution = Repo::institution()->newDataObject($params);
        $id = Repo::institution()->add($institution);
        $institution = Repo::institution()->get($id);
        return $response->withJson(Repo::institution()->getSchemaMap()->map($institution), 200);
    }

    /**
     * Edit an institution
     */
    public function edit(SlimHttpRequest $slimRequest, APIResponse $response, array $args): APIResponse
    {
        $request = $this->getRequest();
        $context = $request->getContext();

        if (!Repo::institution()->exists((int) $args['institutionId'], $context->getId())) {
            return $response->withStatus(404)->withJsonError('api.institutions.404.institutionNotFound');
        }

        $institution = Repo::institution()->get((int) $args['institutionId']);

        $params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_INSTITUTION, $slimRequest->getParsedBody());
        $params['id'] = $institution->getId();
        $params['contextId'] = $context->getId();
        // Convert IP ranges string to array
        if (!empty($params['ipRanges'])) {
            $params['ipRanges'] = $this->convertIpToArray($params['ipRanges']);
        }

        $primaryLocale = $context->getPrimaryLocale();
        $allowedLocales = $context->getSupportedFormLocales();
        $errors = Repo::institution()->validate($institution, $params, $allowedLocales, $primaryLocale);
        if (!empty($errors)) {
            return $response->withStatus(400)->withJson($errors);
        }

        Repo::institution()->edit($institution, $params);
        $institution = Repo::institution()->get($institution->getId());
        return $response->withJson(Repo::institution()->getSchemaMap()->map($institution), 200);
    }

    /**
     * Delete an institution
     */
    public function delete(SlimHttpRequest $slimRequest, APIResponse $response, array $args): APIResponse
    {
        if (!Repo::institution()->exists((int) $args['institutionId'], $this->getRequest()->getContext()->getId())) {
            return $response->withStatus(404)->withJsonError('api.institutions.404.institutionNotFound');
        }

        $institution = Repo::institution()->get((int) $args['institutionId']);
        $institutionProps = Repo::institution()->getSchemaMap()->map($institution);
        Repo::institution()->delete($institution);
        return $response->withJson($institutionProps, 200);
    }

    /**
     * Convert IP ranges string to array
     */
    protected function convertIpToArray(string $ipString): array
    {
        return array_map('trim', explode(PHP_EOL, trim($ipString)));
    }
}
