<?php
// This file is part of Moodle - http://moodle.org/
//
// 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 <http://www.gnu.org/licenses/>.

namespace tool_lp;

use core_competency\api;
use core_external\external_api;
use externallib_advanced_testcase;

defined('MOODLE_INTERNAL') || die();

global $CFG;

require_once($CFG->dirroot . '/webservice/tests/helpers.php');

/**
 * External learning plans webservice API tests.
 *
 * @package tool_lp
 * @copyright 2015 Damyon Wiese
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class externallib_test extends externallib_advanced_testcase {

    /** @var \stdClass $creator User with enough permissions to create insystem context. */
    protected $creator = null;

    /** @var \stdClass $catcreator User with enough permissions to create incategory context. */
    protected $catcreator = null;

    /** @var \stdClass $category Category */
    protected $category = null;

    /** @var \stdClass $category Category */
    protected $othercategory = null;

    /** @var \stdClass $user User with enough permissions to view insystem context */
    protected $user = null;

    /** @var \stdClass $catuser User with enough permissions to view incategory context */
    protected $catuser = null;

    /** @var int Creator role id */
    protected $creatorrole = null;

    /** @var int User role id */
    protected $userrole = null;

    /**
     * Setup function- we will create a course and add an assign instance to it.
     */
    protected function setUp(): void {
        global $DB, $CFG;

        $this->resetAfterTest(true);

        // Create some users.
        $creator = $this->getDataGenerator()->create_user();
        $user = $this->getDataGenerator()->create_user();
        $catuser = $this->getDataGenerator()->create_user();
        $catcreator = $this->getDataGenerator()->create_user();
        $category = $this->getDataGenerator()->create_category();
        $othercategory = $this->getDataGenerator()->create_category();
        $syscontext = \context_system::instance();
        $catcontext = \context_coursecat::instance($category->id);

        // Fetching default authenticated user role.
        $authrole = $DB->get_record('role', array('id' => $CFG->defaultuserroleid));

        // Reset all default authenticated users permissions.
        unassign_capability('moodle/competency:competencygrade', $authrole->id);
        unassign_capability('moodle/competency:competencymanage', $authrole->id);
        unassign_capability('moodle/competency:competencyview', $authrole->id);
        unassign_capability('moodle/competency:planmanage', $authrole->id);
        unassign_capability('moodle/competency:planmanagedraft', $authrole->id);
        unassign_capability('moodle/competency:planmanageown', $authrole->id);
        unassign_capability('moodle/competency:planview', $authrole->id);
        unassign_capability('moodle/competency:planviewdraft', $authrole->id);
        unassign_capability('moodle/competency:planviewown', $authrole->id);
        unassign_capability('moodle/competency:planviewowndraft', $authrole->id);
        unassign_capability('moodle/competency:templatemanage', $authrole->id);
        unassign_capability('moodle/competency:templateview', $authrole->id);
        unassign_capability('moodle/cohort:manage', $authrole->id);
        unassign_capability('moodle/competency:coursecompetencyconfigure', $authrole->id);

        // Creating specific roles.
        $this->creatorrole = create_role('Creator role', 'lpcreatorrole', 'learning plan creator role description');
        $this->userrole = create_role('User role', 'lpuserrole', 'learning plan user role description');

        assign_capability('moodle/competency:competencymanage', CAP_ALLOW, $this->creatorrole, $syscontext->id);
        assign_capability('moodle/competency:coursecompetencyconfigure', CAP_ALLOW, $this->creatorrole, $syscontext->id);
        assign_capability('moodle/competency:planmanage', CAP_ALLOW, $this->creatorrole, $syscontext->id);
        assign_capability('moodle/competency:planmanagedraft', CAP_ALLOW, $this->creatorrole, $syscontext->id);
        assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $this->creatorrole, $syscontext->id);
        assign_capability('moodle/competency:planview', CAP_ALLOW, $this->creatorrole, $syscontext->id);
        assign_capability('moodle/competency:planviewdraft', CAP_ALLOW, $this->creatorrole, $syscontext->id);
        assign_capability('moodle/competency:templatemanage', CAP_ALLOW, $this->creatorrole, $syscontext->id);
        assign_capability('moodle/competency:competencygrade', CAP_ALLOW, $this->creatorrole, $syscontext->id);
        assign_capability('moodle/cohort:manage', CAP_ALLOW, $this->creatorrole, $syscontext->id);

        assign_capability('moodle/competency:competencyview', CAP_ALLOW, $this->userrole, $syscontext->id);
        assign_capability('moodle/competency:templateview', CAP_ALLOW, $this->userrole, $syscontext->id);
        assign_capability('moodle/competency:planviewown', CAP_ALLOW, $this->userrole, $syscontext->id);
        assign_capability('moodle/competency:planviewowndraft', CAP_ALLOW, $this->userrole, $syscontext->id);

        role_assign($this->creatorrole, $creator->id, $syscontext->id);
        role_assign($this->creatorrole, $catcreator->id, $catcontext->id);
        role_assign($this->userrole, $user->id, $syscontext->id);
        role_assign($this->userrole, $catuser->id, $catcontext->id);

        $this->creator = $creator;
        $this->catcreator = $catcreator;
        $this->user = $user;
        $this->catuser = $catuser;
        $this->category = $category;
        $this->othercategory = $othercategory;

        accesslib_clear_all_caches_for_unit_testing();
    }

    public function test_search_users_by_capability(): void {
        global $CFG;
        $this->resetAfterTest(true);

        $dg = $this->getDataGenerator();
        $ux = $dg->create_user();
        $u1 = $dg->create_user(array('idnumber' => 'Cats', 'firstname' => 'Bob', 'lastname' => 'Dyyylan',
            'email' => 'bobbyyy@dyyylan.com', 'phone1' => '123456', 'phone2' => '78910', 'department' => 'Marketing',
            'institution' => 'HQ'));

        // First we search with no capability assigned.
        $this->setUser($ux);
        $result = external::search_users('yyylan', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(0, $result['users']);
        $this->assertEquals(0, $result['count']);

        // Now we assign a different capability.
        $usercontext = \context_user::instance($u1->id);
        $systemcontext = \context_system::instance();
        $customrole = $this->assignUserCapability('moodle/competency:planview', $usercontext->id);

        $result = external::search_users('yyylan', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(0, $result['users']);
        $this->assertEquals(0, $result['count']);

        // Now we assign a matching capability in the same role.
        $usercontext = \context_user::instance($u1->id);
        $this->assignUserCapability('moodle/competency:planmanage', $usercontext->id, $customrole);

        $result = external::search_users('yyylan', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(1, $result['users']);
        $this->assertEquals(1, $result['count']);

        // Now assign another role with the same capability (test duplicates).
        role_assign($this->creatorrole, $ux->id, $usercontext->id);
        $result = external::search_users('yyylan', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(1, $result['users']);
        $this->assertEquals(1, $result['count']);

        // Now lets try a different user with only the role at system level.
        $ux2 = $dg->create_user();
        role_assign($this->creatorrole, $ux2->id, $systemcontext->id);
        $this->setUser($ux2);
        $result = external::search_users('yyylan', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(1, $result['users']);
        $this->assertEquals(1, $result['count']);

        // Now lets try a different user with only the role at user level.
        $ux3 = $dg->create_user();
        role_assign($this->creatorrole, $ux3->id, $usercontext->id);
        $this->setUser($ux3);
        $result = external::search_users('yyylan', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(1, $result['users']);
        $this->assertEquals(1, $result['count']);

        // Switch back.
        $this->setUser($ux);

        // Now add a prevent override (will change nothing because we still have an ALLOW).
        assign_capability('moodle/competency:planmanage', CAP_PREVENT, $customrole, $usercontext->id);
        $result = external::search_users('yyylan', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(1, $result['users']);
        $this->assertEquals(1, $result['count']);

        // Now change to a prohibit override (should prevent access).
        assign_capability('moodle/competency:planmanage', CAP_PROHIBIT, $customrole, $usercontext->id);
        $result = external::search_users('yyylan', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(1, $result['users']);
        $this->assertEquals(1, $result['count']);

    }

    /**
     * Ensures that overrides, as well as system permissions, are respected.
     */
    public function test_search_users_by_capability_the_comeback(): void {
        $this->resetAfterTest();
        $dg = $this->getDataGenerator();

        $master = $dg->create_user();
        $manager = $dg->create_user();
        $slave1 = $dg->create_user(array('lastname' => 'MOODLER'));
        $slave2 = $dg->create_user(array('lastname' => 'MOODLER'));
        $slave3 = $dg->create_user(array('lastname' => 'MOODLER'));

        $syscontext = \context_system::instance();
        $slave1context = \context_user::instance($slave1->id);
        $slave2context = \context_user::instance($slave2->id);
        $slave3context = \context_user::instance($slave3->id);

        // Creating a role giving the site config.
        $roleid = $dg->create_role();
        assign_capability('moodle/site:config', CAP_ALLOW, $roleid, $syscontext->id, true);

        // Create a role override for slave 2.
        assign_capability('moodle/site:config', CAP_PROHIBIT, $roleid, $slave2context->id, true);

        // Assigning the role.
        // Master -> System context.
        // Manager -> User context.
        role_assign($roleid, $master->id, $syscontext);
        role_assign($roleid, $manager->id, $slave1context);

        // Flush accesslib.
        accesslib_clear_all_caches_for_unit_testing();

        // Confirm.
        // Master has system permissions.
        $this->setUser($master);
        $this->assertTrue(has_capability('moodle/site:config', $syscontext));
        $this->assertTrue(has_capability('moodle/site:config', $slave1context));
        $this->assertFalse(has_capability('moodle/site:config', $slave2context));
        $this->assertTrue(has_capability('moodle/site:config', $slave3context));

        // Manager only has permissions in slave 1.
        $this->setUser($manager);
        $this->assertFalse(has_capability('moodle/site:config', $syscontext));
        $this->assertTrue(has_capability('moodle/site:config', $slave1context));
        $this->assertFalse(has_capability('moodle/site:config', $slave2context));
        $this->assertFalse(has_capability('moodle/site:config', $slave3context));

        // Now do the test.
        $this->setUser($master);
        $result = external::search_users('MOODLER', 'moodle/site:config');
        $this->assertCount(2, $result['users']);
        $this->assertEquals(2, $result['count']);
        $this->assertArrayHasKey($slave1->id, $result['users']);
        $this->assertArrayHasKey($slave3->id, $result['users']);

        $this->setUser($manager);
        $result = external::search_users('MOODLER', 'moodle/site:config');
        $this->assertCount(1, $result['users']);
        $this->assertEquals(1, $result['count']);
        $this->assertArrayHasKey($slave1->id, $result['users']);
    }

    public function test_search_users(): void {
        global $CFG;
        $this->resetAfterTest(true);

        $dg = $this->getDataGenerator();
        $ux = $dg->create_user();
        $u1 = $dg->create_user(array('idnumber' => 'Cats', 'firstname' => 'Bob', 'lastname' => 'Dyyylan',
            'email' => 'bobbyyy@dyyylan.com', 'phone1' => '123456', 'phone2' => '78910', 'department' => 'Marketing',
            'institution' => 'HQ'));
        $u2 = $dg->create_user(array('idnumber' => 'Dogs', 'firstname' => 'Alice', 'lastname' => 'Dyyylan',
            'email' => 'alyyyson@dyyylan.com', 'phone1' => '33333', 'phone2' => '77777', 'department' => 'Development',
            'institution' => 'O2'));
        $u3 = $dg->create_user(array('idnumber' => 'Fish', 'firstname' => 'Thomas', 'lastname' => 'Xow',
            'email' => 'fishyyy@moodle.com', 'phone1' => '77777', 'phone2' => '33333', 'department' => 'Research',
            'institution' => 'Bob'));

        // We need to give the user the capability we are searching for on each of the test users.
        $this->setAdminUser();
        $usercontext = \context_user::instance($u1->id);
        $dummyrole = $this->assignUserCapability('moodle/competency:planmanage', $usercontext->id);
        $usercontext = \context_user::instance($u2->id);
        $this->assignUserCapability('moodle/competency:planmanage', $usercontext->id, $dummyrole);
        $usercontext = \context_user::instance($u3->id);
        $this->assignUserCapability('moodle/competency:planmanage', $usercontext->id, $dummyrole);

        $this->setUser($ux);
        $usercontext = \context_user::instance($u1->id);
        $this->assignUserCapability('moodle/competency:planmanage', $usercontext->id, $dummyrole);
        $usercontext = \context_user::instance($u2->id);
        $this->assignUserCapability('moodle/competency:planmanage', $usercontext->id, $dummyrole);
        $usercontext = \context_user::instance($u3->id);
        $this->assignUserCapability('moodle/competency:planmanage', $usercontext->id, $dummyrole);

        $this->setAdminUser();

        // No identity fields.
        $CFG->showuseridentity = '';
        $result = external::search_users('cats', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(0, $result['users']);
        $this->assertEquals(0, $result['count']);

        // Filter by name.
        $CFG->showuseridentity = '';
        $result = external::search_users('dyyylan', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(2, $result['users']);
        $this->assertEquals(2, $result['count']);
        $this->assertEquals($u2->id, $result['users'][0]['id']);
        $this->assertEquals($u1->id, $result['users'][1]['id']);

        // Filter by institution and name.
        $CFG->showuseridentity = 'institution';
        $result = external::search_users('bob', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(2, $result['users']);
        $this->assertEquals(2, $result['count']);
        $this->assertEquals($u1->id, $result['users'][0]['id']);
        $this->assertEquals($u3->id, $result['users'][1]['id']);

        // Filter by id number.
        $CFG->showuseridentity = 'idnumber';
        $result = external::search_users('cats', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(1, $result['users']);
        $this->assertEquals(1, $result['count']);
        $this->assertEquals($u1->id, $result['users'][0]['id']);
        $this->assertEquals($u1->idnumber, $result['users'][0]['idnumber']);
        $this->assertEmpty($result['users'][0]['email']);
        $this->assertEmpty($result['users'][0]['phone1']);
        $this->assertEmpty($result['users'][0]['phone2']);
        $this->assertEmpty($result['users'][0]['department']);
        $this->assertEmpty($result['users'][0]['institution']);

        // Filter by email.
        $CFG->showuseridentity = 'email';
        $result = external::search_users('yyy', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(3, $result['users']);
        $this->assertEquals(3, $result['count']);
        $this->assertEquals($u2->id, $result['users'][0]['id']);
        $this->assertEquals($u2->email, $result['users'][0]['email']);
        $this->assertEquals($u1->id, $result['users'][1]['id']);
        $this->assertEquals($u1->email, $result['users'][1]['email']);
        $this->assertEquals($u3->id, $result['users'][2]['id']);
        $this->assertEquals($u3->email, $result['users'][2]['email']);

        // Filter by any.
        $CFG->showuseridentity = 'idnumber,email,phone1,phone2,department,institution';
        $result = external::search_users('yyy', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(3, $result['users']);
        $this->assertEquals(3, $result['count']);
        $this->assertArrayHasKey('idnumber', $result['users'][0]);
        $this->assertArrayHasKey('email', $result['users'][0]);
        $this->assertArrayHasKey('phone1', $result['users'][0]);
        $this->assertArrayHasKey('phone2', $result['users'][0]);
        $this->assertArrayHasKey('department', $result['users'][0]);
        $this->assertArrayHasKey('institution', $result['users'][0]);

        // Switch to a user that cannot view identity fields.
        $this->setUser($ux);
        $CFG->showuseridentity = 'idnumber,email,phone1,phone2,department,institution';

        // Only names are included.
        $result = external::search_users('fish');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(0, $result['users']);
        $this->assertEquals(0, $result['count']);

        $result = external::search_users('bob', 'moodle/competency:planmanage');
        $result = external_api::clean_returnvalue(external::search_users_returns(), $result);
        $this->assertCount(1, $result['users']);
        $this->assertEquals(1, $result['count']);
        $this->assertEquals($u1->id, $result['users'][0]['id']);
        $this->assertEmpty($result['users'][0]['idnumber']);
        $this->assertEmpty($result['users'][0]['email']);
        $this->assertEmpty($result['users'][0]['phone1']);
        $this->assertEmpty($result['users'][0]['phone2']);
        $this->assertEmpty($result['users'][0]['department']);
        $this->assertEmpty($result['users'][0]['institution']);
    }

    public function test_data_for_user_competency_summary_in_plan(): void {
        global $CFG;

        $this->setUser($this->creator);

        $dg = $this->getDataGenerator();
        $lpg = $dg->get_plugin_generator('core_competency');

        $f1 = $lpg->create_framework();

        $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id')));

        $tpl = $lpg->create_template();
        $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $c1->get('id')));

        $plan = $lpg->create_plan(array('userid' => $this->user->id, 'templateid' => $tpl->get('id'), 'name' => 'Evil'));

        $uc = $lpg->create_user_competency(array('userid' => $this->user->id, 'competencyid' => $c1->get('id')));

        $evidence = \core_competency\external::grade_competency_in_plan($plan->get('id'), $c1->get('id'), 1, true);
        $evidence = \core_competency\external::grade_competency_in_plan($plan->get('id'), $c1->get('id'), 2, true);

        $summary = external::data_for_user_competency_summary_in_plan($c1->get('id'), $plan->get('id'));
        $this->assertTrue($summary->usercompetencysummary->cangrade);
        $this->assertEquals('Evil', $summary->plan->name);
        $this->assertEquals('B', $summary->usercompetencysummary->usercompetency->gradename);
        $this->assertEquals('B', $summary->usercompetencysummary->evidence[0]->gradename);
        $this->assertEquals('A', $summary->usercompetencysummary->evidence[1]->gradename);
    }

    public function test_data_for_user_competency_summary(): void {
        $this->setUser($this->creator);

        $dg = $this->getDataGenerator();
        $lpg = $dg->get_plugin_generator('core_competency');
        $f1 = $lpg->create_framework();
        $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id')));

        $evidence = \core_competency\external::grade_competency($this->user->id, $c1->get('id'), 1, true);
        $evidence = \core_competency\external::grade_competency($this->user->id, $c1->get('id'), 2, true);

        $summary = external::data_for_user_competency_summary($this->user->id, $c1->get('id'));
        $this->assertTrue($summary->cangrade);
        $this->assertEquals('B', $summary->usercompetency->gradename);
        $this->assertEquals('B', $summary->evidence[0]->gradename);
        $this->assertEquals('A', $summary->evidence[1]->gradename);
    }

    public function test_data_for_course_competency_page(): void {
        $this->setAdminUser();

        $dg = $this->getDataGenerator();
        $lpg = $dg->get_plugin_generator('core_competency');
        $f1 = $lpg->create_framework();
        $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id')));
        $course1 = $dg->create_course(array('category' => $this->category->id));
        $cc = api::add_competency_to_course($course1->id, $c1->get('id'));

        $evidence = \core_competency\external::grade_competency($this->user->id, $c1->get('id'), 1, true);
        $evidence = \core_competency\external::grade_competency($this->user->id, $c1->get('id'), 2, true);

        $pagegenerator = $this->getDataGenerator()->get_plugin_generator('mod_page');
        $page = $pagegenerator->create_instance(array('course' => $course1->id));
        $page2 = $pagegenerator->create_instance(array('course' => $course1->id));

        $cm = get_coursemodule_from_instance('page', $page->id);
        $cm2 = get_coursemodule_from_instance('page', $page2->id);
        // Add the competency to the course module.
        $ccm = api::add_competency_to_course_module($cm, $c1->get('id'));
        $summary = external::data_for_course_competencies_page($course1->id, 0);
        $summary2 = external::data_for_course_competencies_page($course1->id, $cm->id);
        $summary3 = external::data_for_course_competencies_page($course1->id, $cm2->id);

        $this->assertEquals(count($summary->competencies), 1);
        $this->assertEquals(count($summary->competencies), count($summary2->competencies));
        $this->assertEquals(count($summary3->competencies), 0);
    }
}
