<?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();

use local_treestudyplan\local\helpers\webservicehelper;
use local_treestudyplan\task\autocohortsync;
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;

require_once($CFG->libdir . '/externallib.php');
/**
 * Webservice class for handling associations of cohorts and users to a studyplan
 */
class associationservice extends external_api {
    /**
     * Capability required to edit study plans
     * @var string
     */
    const CAP_EDIT = "local/treestudyplan:editstudyplan";
    /**
     * Capability required to view studyplans (for other users)
     * @var string
     */
    const CAP_VIEW = "local/treestudyplan:viewuserreports";
    /**
     * Capability required to be linked as coach to a studyplan
     * @var string
     */
    const CAP_COACH = "local/treestudyplan:coach";
    /**
     * Webservice structure to use in describing a user
     */
    public static function user_structure(): external_description {
        return new external_single_structure([
            "id" => new external_value(PARAM_INT, 'user id'),
            "username" => new external_value(PARAM_TEXT, 'username'),
            "firstname" => new external_value(PARAM_TEXT, 'first name'),
            "lastname" => new external_value(PARAM_TEXT, 'last name'),
            "fullname" => new external_value(PARAM_TEXT, 'full name'),
            "idnumber" => new external_value(PARAM_TEXT, 'id number'),
            "email" => new external_value(PARAM_TEXT, 'email address'),
            "lastaccess" => new external_value(
                PARAM_INT,
                'id of last access this user had to any course in the studyplan',
                VALUE_OPTIONAL
            ),
        ]);
    }

    /**
     * Make a webservice user model for a given user
     * @param stdClass $r User DB record
     */
    public static function make_user_model($r) {
        return [
            "id" => $r->id,
            "username" => $r->username,
            "firstname" => $r->firstname,
            "lastname" => $r->lastname,
            "fullname" => \core_user::get_fullname($r),
            "idnumber" => $r->idnumber,
            "email" => $r->email,
        ];
    }

    /**
     * Webservice structure to use in describing a cohort
     */
    public static function cohort_structure(): external_description {
        return new external_single_structure([
            "id" => new external_value(PARAM_INT, 'cohort id'),
            "name" => new external_value(PARAM_TEXT, 'name'),
            "idnumber" => new external_value(PARAM_TEXT, 'id number'),
            "description" => new external_value(PARAM_TEXT, 'description'),
            "visible" => new external_value(PARAM_BOOL, 'is visible'),
            "context" => new external_single_structure([
                "name" => new external_value(PARAM_TEXT, 'context name'),
                "path" => new external_multiple_structure(
                    new external_value(PARAM_TEXT)
                ),
            ], 'context information', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Make a webservice cohort model for a given cohort
     * @param stdClass $r Cohort DB record
     */
    public static function make_cohort_model($r) {
        $ctx = context::instance_by_id($r->contextid);
        if (is_object($ctx)) {
            $ctxpath = array_reverse($ctx->get_parent_context_ids(true));
            if (count($ctxpath) > 1 && $ctxpath[0] == 1) {
                array_shift($ctxpath);
            }

            $result = [
                "id" => $r->id,
                "name" => $r->name,
                "idnumber" => $r->idnumber,
                "description" => $r->description,
                "visible" => $r->visible,
                "context" => [
                    "name" => $ctx->get_context_name(false, false),
                    "path" => array_map(
                        function ($c) {
                            $cx = (object)context::instance_by_id($c);
                            return $cx->get_context_name(false, false);
                        },
                        $ctxpath
                    ),
                ],
            ];

            return $result;
        } else {
            throw new \moodle_exception("");
        }
    }

    /**
     * Get last access time of user to any of the courses in the studyplan
     * @param int $userid ID of user
     * @param int $studyplanid ID of studyplan
     */
    public static function user_lastaccess($userid, $studyplanid = null) {
        global $DB;
        if (!empty($studyplanid)) {
            $lasql = "SELECT MAX(a.timeaccess) FROM {user_lastaccess} a
                        INNER JOIN {local_treestudyplan_item} i ON i.course_id = a.courseid
                        INNER JOIN {local_treestudyplan_line} l ON l.id = i.line_id
                        INNER JOIN {local_treestudyplan_page} p ON l.page_id = p.id
                        WHERE a.userid = :userid AND p.studyplan_id = :studyplanid";
            $lastaccess = $DB->get_field_sql($lasql, ["userid" => $userid, "studyplanid" => $studyplanid]);
        } else {
            $lasql = "SELECT MAX(a.timeaccess) FROM {user_lastaccess} a
                        WHERE a.userid = :userid";
            $lastaccess = $DB->get_field_sql($lasql, ["userid" => $userid]);
        }

        return $lastaccess;
    }

    /**
     * Parameter description for webservice function list_cohort
     */
    public static function list_cohort_parameters(): external_function_parameters {
        return new external_function_parameters([
            'like' => new external_value(PARAM_TEXT, 'search text'),
            'studyplan_id' => new external_value(PARAM_INT, 'id of studyplan to list for'),
        ]);
    }

    /**
     * Return value description for webservice function list_cohort
     */
    public static function list_cohort_returns(): external_description {
        return new external_multiple_structure(self::cohort_structure());
    }

    /**
     * Search cohorts for matching string
     * @param string $like String to match cohorts with
     * @param int $studyplanid Do not include these cohorts
     * @return array
     */
    public static function list_cohort($like, $studyplanid) {
        global $DB;

        // Only allow this if the user has the right to edit in this context.
        $studyplan = studyplan::find_by_id($studyplanid);
        $context = $studyplan->context();
        webservicehelper::require_capabilities(self::CAP_EDIT, $context);

        $pattern = "%{$like}%";

        $params = ["pattern_nm" => \core_text::strtolower($pattern), "pattern_id" => $pattern ];

        $sql = "SELECT DISTINCT c.* from {cohort} c LEFT JOIN {local_treestudyplan_cohort} j ON c.id = j.cohort_id
                WHERE c.visible = 1 AND(LOWER(name) LIKE :pattern_nm OR LOWER(idnumber) LIKE :pattern_id)
                AND  (j.studyplan_id IS NULL OR j.studyplan_id != :exclude_id)";
        $params['exclude_id'] = $studyplanid;

        $cohorts = [];
        $rs = $DB->get_recordset_sql($sql, $params);
        foreach ($rs as $r) {
            $cohorts[] = static::make_cohort_model($r);
        }
        $rs->close();
        return $cohorts;
    }

    /**
     * Parameter description for webservice function find_user
     */
    public static function find_user_parameters(): external_function_parameters {
        return new external_function_parameters([
            'like' => new external_value(PARAM_TEXT, 'search text'),
            'studyplan_id' => new external_value(PARAM_INT, 'id of studyplan to list for'),
        ]);
    }

    /**
     * Return value description for webservice function find_user
     */
    public static function find_user_returns(): external_description {
        return new external_multiple_structure(self::user_structure());
    }

    /**
     * Search users for match
     * @param string $like String to match user firstname/lastname with
     * @param int $studyplanid Id of studyplan to search for
     * @return array
     */
    public static function find_user($like, $studyplanid) {
        global $DB;

        // Only allow this if the user has the right to edit in this context.
        $studyplan = studyplan::find_by_id($studyplanid);
        $context = $studyplan->context();
        webservicehelper::require_capabilities(self::CAP_EDIT, $context);

        $pattern = "%{$like}%";
        $params = ["pattern_fn" => $pattern,
                   "pattern_ln" => $pattern,
                   "pattern_un" => $pattern,
                  ];
        $sql = "SELECT DISTINCT u.* from {user} u LEFT JOIN {local_treestudyplan_user} j ON u.id = j.user_id
                WHERE u.deleted != 1 AND (firstname LIKE :pattern_fn OR lastname LIKE :pattern_ln OR username LIKE :pattern_un)
                AND  (j.studyplan_id IS NULL OR j.studyplan_id != :exclude_id)";
        $params['exclude_id'] = $studyplanid;

        $users = [];
        $rs = $DB->get_recordset_sql($sql, $params);
        foreach ($rs as $r) {
            $users[] = static::make_user_model($r);
        }
        $rs->close();

        self::sortusermodels($users);
        return $users;
    }

    /**
     * Parameter description for webservice function connect_cohort
     */
    public static function connect_cohort_parameters(): external_function_parameters {
        return new external_function_parameters([
            "studyplan_id" => new external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
            "cohort_id" => new external_value(PARAM_INT, 'id of cohort to link', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Return value description for webservice function connect_cohort
     */
    public static function connect_cohort_returns(): external_description {
        return new external_single_structure([
            "success" => new external_value(PARAM_BOOL, 'operation completed succesfully'),
            "msg" => new external_value(PARAM_TEXT, 'message'),
        ]);
    }

    /**
     * Connect a cohort to a studyplan
     * @param mixed $studyplanid Id of studyplan
     * @param mixed $cohortid Id of cohort
     * @return array Success/fail model
     */
    public static function connect_cohort($studyplanid, $cohortid) {
        global $DB;

        $studyplan = studyplan::find_by_id($studyplanid);
        webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());

        if (!$DB->record_exists('local_treestudyplan_cohort', ['studyplan_id' => $studyplanid, 'cohort_id' => $cohortid])) {
            $DB->insert_record('local_treestudyplan_cohort', [
                'studyplan_id' => $studyplanid,
                'cohort_id' => $cohortid,
            ]);

            $studyplan->mark_csync_changed();
            return ['success' => true, 'msg' => 'Cohort connected'];
        } else {
            return ['success' => true, 'msg' => 'Cohort already connected'];
        }
    }

    /**
     * Parameter description for webservice function disconnect_cohort
     */
    public static function disconnect_cohort_parameters(): external_function_parameters {
        return new external_function_parameters([
            "studyplan_id" => new external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
            "cohort_id" => new external_value(PARAM_INT, 'id of cohort to link', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Return value description for webservice function disconnect_cohort
     */
    public static function disconnect_cohort_returns(): external_description {
        return new external_single_structure([
            "success" => new external_value(PARAM_BOOL, 'operation completed succesfully'),
            "msg" => new external_value(PARAM_TEXT, 'message'),
        ]);
    }

    /**
     * Disconnect a cohort from a studyplan
     * @param mixed $studyplanid Id of studyplan
     * @param mixed $cohortid Id of cohort
     * @return array Success/fail model
     */
    public static function disconnect_cohort($studyplanid, $cohortid) {
        global $DB;

        $studyplan = studyplan::find_by_id($studyplanid);
        webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());

        if ($DB->record_exists('local_treestudyplan_cohort', ['studyplan_id' => $studyplanid, 'cohort_id' => $cohortid])) {
            $DB->delete_records('local_treestudyplan_cohort', [
                'studyplan_id' => $studyplanid,
                'cohort_id' => $cohortid,
            ]);

            $studyplan->mark_csync_changed();

            return ['success' => true, 'msg' => 'Cohort Disconnected'];
        } else {
            return ['success' => true, 'msg' => 'Connection does not exist'];
        }
    }

    /**
     * Parameter description for webservice function connect_user
     */
    public static function connect_user_parameters(): external_function_parameters {
        return new external_function_parameters([
            "studyplan_id" => new external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
            "user_id" => new external_value(PARAM_INT, 'id of user to link', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Return value description for webservice function connect_user
     */
    public static function connect_user_returns(): external_description {
        return new external_single_structure([
            "success" => new external_value(PARAM_BOOL, 'operation completed succesfully'),
            "msg" => new external_value(PARAM_TEXT, 'message'),
        ]);
    }

    /**
     * Connect a user to a studyplan
     * @param mixed $studyplanid Id of studyplan
     * @param mixed $userid Id of user
     * @return array Success/fail model
     */
    public static function connect_user($studyplanid, $userid) {
        global $DB;

        $studyplan = studyplan::find_by_id($studyplanid);
        webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());

        if (!$DB->record_exists('local_treestudyplan_user', ['studyplan_id' => $studyplanid, 'user_id' => $userid])) {
            $DB->insert_record('local_treestudyplan_user', [
                'studyplan_id' => $studyplanid,
                'user_id' => $userid,
            ]);
            $studyplan->mark_csync_changed();

            return ['success' => true, 'msg' => 'Cohort connected'];
        } else {
            return ['success' => true, 'msg' => 'Cohort already connected'];
        }
    }

    /**
     * Parameter description for webservice function disconnect_user
     */
    public static function disconnect_user_parameters(): external_function_parameters {
        return new external_function_parameters([
            "studyplan_id" => new external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
            "user_id" => new external_value(PARAM_INT, 'id of user to link', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Return value description for webservice function disconnect_user
     */
    public static function disconnect_user_returns(): external_description {
        return new external_single_structure([
            "success" => new external_value(PARAM_BOOL, 'operation completed succesfully'),
            "msg" => new external_value(PARAM_TEXT, 'message'),
        ]);
    }

    /**
     * Disconnect a user from a studyplan
     * @param mixed $studyplanid Id of studyplan
     * @param mixed $userid Id of user
     * @return array Success/fail model
     */
    public static function disconnect_user($studyplanid, $userid) {
        global $DB;
        $studyplan = studyplan::find_by_id($studyplanid);
        webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());

        if ($DB->record_exists('local_treestudyplan_user', ['studyplan_id' => $studyplanid, 'user_id' => $userid])) {
            $DB->delete_records('local_treestudyplan_user', [
                'studyplan_id' => $studyplanid,
                'user_id' => $userid,
            ]);

            $studyplan->mark_csync_changed();

            return ['success' => true, 'msg' => 'User Disconnected'];
        } else {
            return ['success' => true, 'msg' => 'Connection does not exist'];
        }
    }

    /**
     * Parameter description for webservice function associated_users
     */
    public static function associated_users_parameters(): external_function_parameters {
        return new external_function_parameters([
            "studyplan_id" => new external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Return value description for webservice function associated_users
     */
    public static function associated_users_returns(): external_description {
        return new external_multiple_structure(self::user_structure());
    }

    /**
     * List all users associated to a studyplan
     * @param mixed $studyplanid Id of studyplan
     * @return array
     */
    public static function associated_users($studyplanid) {
        global $DB;
        $studyplan = studyplan::find_by_id($studyplanid);
        if ($studyplan->is_coach()) {
            external_api::validate_context($studyplan->context());
        } else {
            webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());
        }

        $sql = "SELECT DISTINCT u.*, t.lastaccess as lastaccess
                FROM {user} u INNER JOIN {local_treestudyplan_user} j ON j.user_id = u.id
                LEFT JOIN (SELECT MAX(a.timeaccess) as lastaccess, a.userid as userid FROM {user_lastaccess} a
                    INNER JOIN {local_treestudyplan_item} i ON i.course_id = a.courseid
                    INNER JOIN {local_treestudyplan_line} l ON l.id = i.line_id
                    INNER JOIN {local_treestudyplan_page} p ON l.page_id = p.id
                    GROUP BY a.userid
                    ) t ON t.userid = u.id
                WHERE j.studyplan_id = :studyplan_id AND u.deleted != 1
                ORDER BY u.lastname, u.firstname";
        $rs = $DB->get_recordset_sql($sql, ['studyplan_id' => $studyplanid]);

        $users = [];
        foreach ($rs as $r) {
            $user = self::make_user_model($r);
            $user["lastaccess"] = $r->lastaccess;
            $users[] = $user;
        }
        $rs->close();
        self::sortusermodels($users);
        return $users;
    }

    /**
     * Parameter description for webservice function associated_cohorts
     */
    public static function associated_cohorts_parameters(): external_function_parameters {
        return new external_function_parameters([
            "studyplan_id" => new external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Return value description for webservice function associated_cohorts
     */
    public static function associated_cohorts_returns(): external_description {
        return new external_multiple_structure(self::cohort_structure());
    }

    /**
     * List all cohorts associated to a studyplan
     * @param mixed $studyplanid Id of studyplan
     * @return array
     */
    public static function associated_cohorts($studyplanid) {
        global $DB;
        $studyplan = studyplan::find_by_id($studyplanid);
        webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());

        $sql = "SELECT DISTINCT c.* FROM {cohort} c INNER JOIN {local_treestudyplan_cohort} j ON j.cohort_id = c.id
                WHERE j.studyplan_id = :studyplan_id";
        $rs = $DB->get_recordset_sql($sql, ['studyplan_id' => $studyplanid]);
        $cohorts = [];
        foreach ($rs as $c) {
            $cohorts[] = self::make_cohort_model($c);
        }
        $rs->close();
        return $cohorts;
    }

    /**
     * Parameter description for webservice function all_associated
     */
    public static function all_associated_parameters(): external_function_parameters {
        return new external_function_parameters([
            "studyplan_id" => new external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Return value description for webservice function all_associated
     */
    public static function all_associated_returns(): external_description {
        return new external_multiple_structure(self::user_structure());
    }

    /**
     * List all users associated to a studyplan through either a cohort or directly
     * @param mixed $studyplanid Id of studyplan
     * @return array
     */
    public static function all_associated($studyplanid) {
        global $DB;

        $studyplan = studyplan::find_by_id($studyplanid);
        webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());

        $users = [];
        // SQL JOIN script selecting all users that have a cohort linked to this studyplan .
        // Or are directly linked.
        $sql = "SELECT DISTINCT u.id, u.username, u.firstname, u.lastname, u.idnumber, u.email
                FROM {user} u
                LEFT JOIN {cohort_members}              cm ON u.id = cm.userid
                LEFT JOIN {local_treestudyplan_cohort}  tc ON cm.cohortid = tc.cohort_id
                LEFT JOIN {local_treestudyplan_user}    tu ON u.id = tu.user_id
                WHERE ( tc.studyplan_id = :studyplanid
                    OR tu.studyplan_id = :studyplanidtoo )
                    AND u.deleted != 1
                ORDER BY u.lastname, u.firstname";
        $rs = $DB->get_recordset_sql($sql, ["studyplanid" => $studyplan->id(), "studyplanidtoo" => $studyplan->id()]);

        foreach ($rs as $u) {
            $users[] = self::make_user_model($u);
        }
        $rs->close();

        self::sortusermodels($users);
        return $users;
    }

    /**
     * Parameter description for webservice function all_associated
     */
    public static function all_associated_grouped_parameters(): external_function_parameters {
        return new external_function_parameters([
            "studyplan_id" => new external_value(PARAM_INT, 'id of studyplan'),
        ]);
    }

    /**
     * Return value description for webservice function all_associated
     */
    public static function all_associated_grouped_returns(): external_description {
        return new external_multiple_structure(new external_single_structure([
            'id' => new external_value(PARAM_INT, 'id of group'),
            'label' => new external_value(PARAM_TEXT, 'group label'),
            'users' => new external_multiple_structure(self::user_structure()),
        ]));
    }

    /**
     * List all users associated to a studyplan through either a cohort or directly
     * @param mixed $studyplanid Id of studyplan
     * @return array
     */
    public static function all_associated_grouped($studyplanid) {
        global $DB;

        $studyplan = studyplan::find_by_id($studyplanid);
        if ($studyplan->is_coach()) {
            external_api::validate_context($studyplan->context());
        } else {
            webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());
        }

        $userlist = [ 0 =>
            [
                'id' => 0,
                'label' => get_string("individuals", 'local_treestudyplan'),
                'users' => self::associated_users($studyplanid),
            ],
        ];

        $sql = "SELECT DISTINCT u.id, u.username, u.firstname, u.lastname, u.idnumber, u.email,
                                c.id as cohortid, c.name as cohortname, t.lastaccess as lastaccess
                FROM {user} u
                LEFT JOIN {cohort_members} cm ON u.id = cm.userid
                LEFT JOIN {cohort} c ON c.id = cm.cohortid
                LEFT JOIN {local_treestudyplan_cohort} j on j.cohort_id = c.id
                LEFT JOIN (SELECT MAX(a.timeaccess) as lastaccess, a.userid as userid FROM {user_lastaccess} a
                    INNER JOIN {local_treestudyplan_item} i ON i.course_id = a.courseid
                    INNER JOIN {local_treestudyplan_line} l ON l.id = i.line_id
                    INNER JOIN {local_treestudyplan_page} p ON l.page_id = p.id
                    GROUP BY a.userid
                    ) t ON t.userid = u.id
                WHERE j.studyplan_id = :studyplan_id AND u.deleted != 1
                ORDER BY cohortname, u.lastname, u.firstname;";
        $rs = $DB->get_recordset_sql($sql, ['studyplan_id' => $studyplanid]);
        foreach ($rs as $r) {
            $user = self::make_user_model($r);
            $user["lastaccess"] = empty($r->lastaccess) ? 0 : $r->lastaccess;

            if (!array_key_exists($r->cohortid, $userlist)) {
                $userlist[$r->cohortid] = [
                    'id' => $r->cohortid,
                    'label' => $r->cohortname,
                    'users' => [],
                ];
            }
            $userlist[$r->cohortid]["users"][] = $user;
        }
        $rs->close();
        return $userlist;
    }

    /**
     * Sort a list of user models by firstname->lastname
     * @param array $list Reference to list of user models
     */
    public static function sortusermodels(&$list) {
        return usort($list, function ($a, $b) {
            $m = [];
            if (preg_match("/.*?([A-Z].*)/", $a['lastname'], $m)) {
                $sortlna = $m[1];
            } else {
                $sortlna = $a['lastname'];
            }
            if (preg_match("/.*?([A-Z].*)/", $b['lastname'], $m)) {
                $sortlnb = $m[1];
            } else {
                $sortlnb = $b['lastname'];
            }
            $cmp = $sortlna <=> $sortlnb;
            return  ($cmp != 0) ? $cmp : $a['firstname'] <=> $b['firstname'];
        });
    }

    /**
     * Parameter description for webservice function cascade_cohortsync
     */
    public static function cascade_cohortsync_parameters(): external_function_parameters {
        return new external_function_parameters([
            "studyplan_id" => new external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Return value description for webservice function cascade_cohortsync
     */
    public static function cascade_cohortsync_returns(): external_description {
        return success::structure();
    }

    /**
     * Perform cascading cohort sync for a given studyplan
     * This adds a cohort sync enrolment to all linked cohorts for each course listed in the studyplan
     * @param mixed $studyplanid Id of studyplan
     * @return array Success/fail model
     */
    public static function cascade_cohortsync($studyplanid) {
        $studyplan = studyplan::find_by_id($studyplanid);
        webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());

        autocohortsync::syncplan($studyplan);

        return success::success()->model();
    }

    /**
     * Parameter description for webservice function connect_user
     */
    public static function connect_coach_parameters(): external_function_parameters {
        return new external_function_parameters([
            "studyplan_id" => new external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
            "user_id" => new external_value(PARAM_INT, 'id of user to link', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Return value description for webservice function connect_user
     */
    public static function connect_coach_returns(): external_description {
        return new external_single_structure([
            "success" => new external_value(PARAM_BOOL, 'operation completed succesfully'),
            "msg" => new external_value(PARAM_TEXT, 'message'),
        ]);
    }

    /**
     * Connect a user to a studyplan
     * @param mixed $studyplanid Id of studyplan
     * @param mixed $userid Id of user
     * @return array Success/fail model
     */
    public static function connect_coach($studyplanid, $userid) {
        global $DB;

        $studyplan = studyplan::find_by_id($studyplanid);
        webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());

        $user = $DB->get_record("user", ["id" => $userid]);
        if (has_capability(self::CAP_COACH, $studyplan->context(), $user)) {
            if (!$DB->record_exists('local_treestudyplan_coach', ['studyplan_id' => $studyplanid, 'user_id' => $userid])) {
                $DB->insert_record('local_treestudyplan_coach', [
                    'studyplan_id' => $studyplanid,
                    'user_id' => $userid,
                ]);

                return ['success' => true, 'msg' => 'User connected as coach'];
            } else {
                return ['success' => true, 'msg' => 'User already connected as coach'];
            }
        } else {
            return ['success' => false, 'msg' => 'User does not have coach capability in this studyplan\'s context'];
        }
    }

    /**
     * Parameter description for webservice function disconnect_user
     */
    public static function disconnect_coach_parameters(): external_function_parameters {
        return new external_function_parameters([
            "studyplan_id" => new external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
            "user_id" => new external_value(PARAM_INT, 'id of user to link', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Return value description for webservice function disconnect_user
     */
    public static function disconnect_coach_returns(): external_description {
        return new external_single_structure([
            "success" => new external_value(PARAM_BOOL, 'operation completed succesfully'),
            "msg" => new external_value(PARAM_TEXT, 'message'),
        ]);
    }

    /**
     * Disconnect a user from a studyplan
     * @param mixed $studyplanid Id of studyplan
     * @param mixed $userid Id of user
     * @return array Success/fail model
     */
    public static function disconnect_coach($studyplanid, $userid) {
        global $DB;
        $studyplan = studyplan::find_by_id($studyplanid);
        webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());

        if ($DB->record_exists('local_treestudyplan_coach', ['studyplan_id' => $studyplanid, 'user_id' => $userid])) {
            $DB->delete_records('local_treestudyplan_coach', [
                'studyplan_id' => $studyplanid,
                'user_id' => $userid,
            ]);

            return ['success' => true, 'msg' => 'User Disconnected as coach'];
        } else {
            return ['success' => true, 'msg' => 'Connection does not exist'];
        }
    }

    /**
     * Parameter description for webservice function find_user
     */
    public static function find_coach_parameters(): external_function_parameters {
        return new external_function_parameters([
            'like' => new external_value(PARAM_TEXT, 'search text'),
            'studyplan_id' => new external_value(PARAM_INT, 'studyplan id to associate for'),
        ]);
    }

    /**
     * Return value description for webservice function find_user
     */
    public static function find_coach_returns(): external_description {
        return new external_multiple_structure(self::user_structure());
    }

    /**
     * Search users for match
     * @param string $like String to match user firstname/lastname with
     * @param int $studyplanid Id of studyplan to search for
     * @return array
     */
    public static function find_coach($like, $studyplanid) {
        global $DB;

        // Only allow this if the user has the right to edit in this context.
        $studyplan = studyplan::find_by_id($studyplanid);
        $context = $studyplan->context();
        webservicehelper::require_capabilities(self::CAP_EDIT, $context);

        $pattern = "%{$like}%";
        $params = ["pattern_fn" => $pattern,
                   "pattern_ln" => $pattern,
                   "pattern_un" => $pattern,
                  ];
        $sql = "SELECT DISTINCT u.* FROM {user} u LEFT JOIN {local_treestudyplan_coach} j ON u.id = j.user_id
                WHERE u.deleted != 1 AND (firstname LIKE :pattern_fn OR lastname LIKE :pattern_ln OR username LIKE :pattern_un)";
        if (isset($excludeid) && is_numeric($excludeid)) {
            $sql .= " AND  (j.studyplan_id IS NULL OR j.studyplan_id != :exclude_id)";
            $params['exclude_id'] = $studyplan;
        }

        $users = [];
        $rs = $DB->get_recordset_sql($sql, $params);
        foreach ($rs as $r) {
            if (has_capability(self::CAP_COACH, $context, $r)) {
                $users[] = static::make_user_model($r);
            }
        }
        $rs->close();

        self::sortusermodels($users);
        return $users;
    }
    /**
     * Parameter description for webservice function associated_users
     */
    public static function associated_coaches_parameters(): external_function_parameters {
        return new external_function_parameters([
            "studyplan_id" => new external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Return value description for webservice function associated_users
     */
    public static function associated_coaches_returns(): external_description {
        return new external_multiple_structure(self::user_structure());
    }

    /**
     * List all users associated to a studyplan
     * @param mixed $studyplanid Id of studyplan
     * @return array
     */
    public static function associated_coaches($studyplanid) {
        global $DB;
        $studyplan = studyplan::find_by_id($studyplanid);
        webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());

        $sql = "SELECT DISTINCT u.* FROM {user} u INNER JOIN {local_treestudyplan_coach} j ON j.user_id = u.id
                WHERE j.studyplan_id = :studyplan_id AND u.deleted != 1
                ORDER BY u.lastname, u.firstname";
        $rs = $DB->get_recordset_sql($sql, ['studyplan_id' => $studyplanid]);

        $users = [];
        foreach ($rs as $u) {
            $user = self::make_user_model($u);
            $users[] = $user;
        }
        $rs->close();
        self::sortusermodels($users);
        return $users;
    }
}
