<?php
// This file is part of the Studyplan plugin for Moodle
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <https://www.gnu.org/licenses/>.

/**
 * Webservice class for handling associations of cohorts and users to a studyplan
 * @package    local_treestudyplan
 * @copyright  2023 P.M. Kuipers
 * @license    https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */


namespace local_treestudyplan;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/externallib.php');

use core_user\fields;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_description;
use core_external\external_value;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core\context;
use core\context\system as context_system;
use core\context\course as context_course;
use core\context\coursecat as context_coursecat;

/**
 * Webservice class for handling associations of cohorts and users to a studyplan
 */
class shareservice extends external_api {
    /**
     * Capability required to edit study plans
     * @var string
     */
    const CAP_MANAGE = "local/treestudyplan:manageshares";

    /**
     * Gets an array of user matching search criteria
     *
     * @param string $search The search term, if any
     * @param bool $searchanywhere Can the search term be anywhere, or must it be at the start
     * @param int $page Defaults to 0
     * @param int $perpage Defaults to 25
     * @return array with two or three elements:
     *      int totalusers Number users matching the search.
     *      array users List of user objects returned by the query.
     *      boolean moreusers True if there are still more users, otherwise is False.
     * @throws dml_exception
     */
    public static function find_potential_users($search = '', $searchanywhere = false, $page = 0, $perpage = 25) {
        global $DB;
        $context = context_system::instance();
        [$ufields, $joins, $params, $wherecondition] = self::get_basic_user_search_conditions($search, $searchanywhere);
        [$sort, $sortparams] = users_order_by_sql('u', $search, $context);
        $searchparams = array_merge($params, $sortparams);

        $countsql =
               "SELECT COUNT(1)
                FROM {user} u
                {$joins}
                WHERE {$wherecondition}";

        $searchsql =
               "SELECT {$ufields}
                FROM {user} u
                {$joins}
                WHERE {$wherecondition}
                ORDER BY {$sort}";

        $totalusers = 0;
        $moreusers = false;
        $results = [];

        $availableusers = $DB->get_records_sql($searchsql, $searchparams, ($page * $perpage), $perpage + 1);
        if ($availableusers) {
            $totalusers = count($availableusers);
            $moreusers = $totalusers > $perpage;

            if ($moreusers) {
                // We need to discard the last record.
                array_pop($availableusers);
                $totalusers = $DB->count_records_sql($countsql, $params);
            }
        }

        $results['users'] = $availableusers;
        $results['moreusers'] = $moreusers;
        $results['totalusers'] = $totalusers;

        return $results;
    }

    /**
     * Helper method  to get main user search conditions
     *
     * @param string $search the search term, if any.
     * @param bool $searchanywhere Can the search term be anywhere, or must it be at the start.
     * @return array with three elements:
     *     string list of fields to SELECT,
     *     string possible database joins for user fields
     *     string contents of SQL WHERE clause,
     *     array query params. Note that the SQL snippets use named parameters.
     */
    public static function get_basic_user_search_conditions($search, $searchanywhere) {
        global $DB, $CFG;
        $context = context_system::instance();
        // Prepare some search conditions. (Adapted from enrol/locallib.php).

        // Get custom user field SQL used for querying all the fields we need (identity, name, and
        // user picture).
        // Get userfields.
        $userfields = fields::for_identity($context)->with_name()
            ->with_userpic()
            ->excluding('username', 'lastaccess', 'maildisplay');

        // Get SQL codes to help find userfields.
        ['selects' => $fieldselects,
         'joins' => $fieldjoins,
         'params' => $params,
         'mappings' => $mappings] = (array)$userfields->get_sql('u', true, '', '', false);

        // Searchable fields are only the identity and name ones (not userpic, and without exclusions).
        $searchablefields = fields::for_identity($context)->with_name();
        $searchable = array_fill_keys($searchablefields->get_required_fields(), true);

        // Add username field to the mappings if it is searchable.
        if (array_key_exists('username', $searchable)) {
            // Add the username into the mappings list from the other query, because it was excluded.
            $mappings['username'] = 'u.username';
        }

        // Add some additional sensible conditions.
        $tests = ["u.id <> :guestid", 'u.deleted = 0', 'u.confirmed = 1'];
        $params['guestid'] = $CFG->siteguest;

        // Prepare the search query parameters.
        if (!empty($search)) {
            $conditions = [];
            // Include identity and name fields as conditions.
            foreach ($mappings as $fieldname => $fieldsql) {
                if (array_key_exists($fieldname, $searchable)) {
                    $conditions[] = $fieldsql;
                }
            }
            $conditions[] = $DB->sql_fullname('u.firstname', 'u.lastname');
            if ($searchanywhere) {
                $searchparam = '%' . $search . '%';
            } else {
                $searchparam = $search . '%';
            }
            $i = 0;
            foreach ($conditions as $key => $condition) {
                $conditions[$key] = $DB->sql_like($condition, ":con{$i}00", false);
                $params["con{$i}00"] = $searchparam;
                $i++;
            }
            $tests[] = '(' . implode(' OR ', $conditions) . ')';
        }
        $wherecondition = implode(' AND ', $tests);

        $selects = $fieldselects . ', u.username, u.lastaccess, u.maildisplay';
        return [$selects, $fieldjoins, $params, $wherecondition];
    }


    /**
     * Returns description of method result value
     *
     * @return external_multiple_structure
     */
    public static function find_users_returns() {
        global $CFG;
        require_once($CFG->dirroot . '/user/externallib.php');
        return new external_multiple_structure(\core_user_external::user_description());
    }

    /**
     * Returns description of method parameters value
     *
     * @return external_function_parameters
     */
    public static function find_users_parameters() {
        return new external_function_parameters(
            [
                'search' => new external_value(PARAM_RAW, 'query'),
                'searchanywhere' => new external_value(PARAM_BOOL, 'find a match anywhere, or only at the beginning'),
                'page' => new external_value(PARAM_INT, 'Page number'),
                'perpage' => new external_value(PARAM_INT, 'Number per page'),
            ]
        );
    }

    /**
     * Get potential users.
     *
     * @param string $search The query
     * @param boolean $searchanywhere Match anywhere in the string
     * @param int $page Page number
     * @param int $perpage Max per page
     * @return array An array of users
     */
    public static function find_users($search, $searchanywhere, $page, $perpage) {
        global $CFG;

        require_once($CFG->dirroot . '/enrol/locallib.php');
        require_once($CFG->dirroot . '/user/lib.php');

        $params = self::validate_parameters(
            self::find_users_parameters(),
            [
                'search' => $search,
                'searchanywhere' => $searchanywhere,
                'page' => $page,
                'perpage' => $perpage,
            ]
        );
        $context = context_system::instance();
        self::validate_context($context);
        require_capability(self::CAP_MANAGE, $context);

        $users = self::find_potential_users(
            $params['search'],
            $params['searchanywhere'],
            $params['page'],
            $params['perpage']
        );

        $results = [];
        // Add also extra user fields.
        $identityfields = \core_user\fields::get_identity_fields($context, true);
        $customprofilefields = [];
        foreach ($identityfields as $key => $value) {
            if ($fieldname = \core_user\fields::match_custom_field($value)) {
                unset($identityfields[$key]);
                $customprofilefields[$fieldname] = true;
            }
        }
        if ($customprofilefields) {
            $identityfields[] = 'customfields';
        }
        $requiredfields = array_merge(
            ['id', 'fullname', 'profileimageurl', 'profileimageurlsmall'],
            $identityfields
        );
        foreach ($users['users'] as $user) {
            if ($userdetails = user_get_user_details($user, null, $requiredfields)) {
                // For custom fields, only return the ones we actually need.
                if ($customprofilefields && array_key_exists('customfields', $userdetails)) {
                    foreach ($userdetails['customfields'] as $key => $data) {
                        if (!array_key_exists($data['shortname'], $customprofilefields)) {
                            unset($userdetails['customfields'][$key]);
                        }
                    }
                    $userdetails['customfields'] = array_values($userdetails['customfields']);
                }
                $results[] = $userdetails;
            }
        }
        return $results;
    }

    /**
     * Get user details for specific user
     *
     * @param object $user User record
     * @return string
     */
    public static function get_user_details($user) {
        $context = context_system::instance();
        $identityfields = \core_user\fields::get_identity_fields($context, true);
        $customprofilefields = [];
        foreach ($identityfields as $key => $value) {
            if ($fieldname = \core_user\fields::match_custom_field($value)) {
                unset($identityfields[$key]);
                $customprofilefields[$fieldname] = true;
            }
        }
        if ($customprofilefields) {
            $identityfields[] = 'customfields';
        }
        $requiredfields = array_merge(
            ['id', 'fullname', 'profileimageurl', 'profileimageurlsmall'],
            $identityfields
        );

        if ($userdetails = user_get_user_details($user, null, $requiredfields)) {
            // For custom fields, only return the ones we actually need.
            if ($customprofilefields && array_key_exists('customfields', $userdetails)) {
                foreach ($userdetails['customfields'] as $key => $data) {
                    if (!array_key_exists($data['shortname'], $customprofilefields)) {
                        unset($userdetails['customfields'][$key]);
                    }
                }
                $userdetails['customfields'] = array_values($userdetails['customfields']);
            }
        }
        return $userdetails;
    }
}
