<?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/>.

/**
 * Privacy tests for core_grading.
 *
 * @package    core_grading
 * @category   test
 * @copyright  2018 Sara Arjona <sara@moodle.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
namespace core_grading\privacy;

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

global $CFG;

use core_privacy\tests\provider_testcase;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use core_grading\privacy\provider;

/**
 * Privacy tests for core_grading.
 *
 * @copyright  2018 Sara Arjona <sara@moodle.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class provider_test extends provider_testcase {

    /** @var stdClass User without data. */
    protected $user0;

    /** @var stdClass User with data. */
    protected $user1;

    /** @var stdClass User with data. */
    protected $user2;

    /** @var context context_module of an activity without grading definitions. */
    protected $instancecontext0;

    /** @var context context_module of the activity where the grading definitions are. */
    protected $instancecontext1;

    /** @var context context_module of the activity where the grading definitions are. */
    protected $instancecontext2;

    /**
     * Test getting the context for the user ID related to this plugin.
     */
    public function test_get_contexts_for_userid() {
        global $DB;

        $this->resetAfterTest();
        $this->grading_setup_test_scenario_data();
        $this->assertCount(2, $DB->get_records('grading_definitions'));

        // User1 has created grading definitions for instance1 and instance2.
        $contextlist = provider::get_contexts_for_userid($this->user1->id);
        $this->assertCount(2, $contextlist);
        $this->assertContainsEquals($this->instancecontext1->id, $contextlist->get_contextids());
        $this->assertContainsEquals($this->instancecontext2->id, $contextlist->get_contextids());
        $this->assertNotContainsEquals($this->instancecontext0->id, $contextlist->get_contextids());

        // User2 has only modified grading definitions for instance2.
        $contextlist = provider::get_contexts_for_userid($this->user2->id);
        $this->assertCount(1, $contextlist);
        $this->assertContainsEquals($this->instancecontext2->id, $contextlist->get_contextids());

        // User0 hasn't created or modified any grading definition.
        $contextlist = provider::get_contexts_for_userid($this->user0->id);
        $this->assertCount(0, $contextlist);
    }

    /**
     * Test retrieval of user ids in a given context.
     */
    public function test_get_users_in_context() {
        $this->resetAfterTest();
        $this->grading_setup_test_scenario_data();
        // Instance two has one user who created the definitions and another who modified it.
        $userlist = new \core_privacy\local\request\userlist($this->instancecontext2, 'core_grading');
        provider::get_users_in_context($userlist);
        // Check that we get both.
        $this->assertCount(2, $userlist->get_userids());
    }

    /**
     * Export for a user with no grading definitions created or modified will not have any data exported.
     */
    public function test_export_user_data_no_content() {
        $this->resetAfterTest();

        $user = $this->getDataGenerator()->create_user();
        $this->setUser($user);
        $context = \context_system::instance();

        /** @var \core_privacy\tests\request\content_writer $writer */
        $writer = writer::with_context($context);
        $this->assertFalse($writer->has_any_data());
        $this->export_context_data_for_user($user->id, $context, 'core_grading');
        $this->assertFalse(writer::with_context($context)->has_any_data());
    }

    /**
     * Test that data is exported correctly for this plugin.
     */
    public function test_export_user_data() {
        global $DB;

        $this->resetAfterTest();
        $now = time();
        $defnameprefix = 'fakename';
        $this->grading_setup_test_scenario_data($defnameprefix, $now);
        $this->assertCount(2, $DB->get_records('grading_definitions'));

        // Validate exported data: instance1 - user0 has NO data.
        $this->setUser($this->user0);
        writer::reset();
        $writer = writer::with_context($this->instancecontext1);
        $this->assertFalse($writer->has_any_data());
        $this->export_context_data_for_user($this->user0->id, $this->instancecontext1, 'core_grading');
        $data = $writer->get_data([get_string('gradingmethod', 'grading')]);
        $this->assertEmpty($data);

        // Validate exported data: instance0 - user1 has NO data.
        $this->setUser($this->user1);
        writer::reset();
        $writer = writer::with_context($this->instancecontext0);
        $this->assertFalse($writer->has_any_data());
        $this->export_context_data_for_user($this->user1->id, $this->instancecontext0, 'core_grading');
        $data = $writer->get_data([get_string('gradingmethod', 'grading')]);
        $this->assertEmpty($data);

        // Validate exported data: instance1 - user1 has data (user has created and modified it).
        writer::reset();
        $writer = writer::with_context($this->instancecontext1);
        $this->assertFalse($writer->has_any_data());
        $this->export_context_data_for_user($this->user1->id, $this->instancecontext1, 'core_grading');
        $data = $writer->get_data([get_string('gradingmethod', 'grading')]);
        $this->assertCount(1, $data->definitions);

        $firstkey = reset($data->definitions);
        $this->assertNotEmpty($firstkey->name);
        $this->assertEquals('test_method', $firstkey->method);
        $this->assertEquals(transform::datetime($now), $firstkey->timecreated);
        $this->assertEquals($this->user1->id, $firstkey->usercreated);
        $this->assertEquals($defnameprefix.'1', $firstkey->name);

        // Validate exported data: instance2 - user1 has data (user has created it).
        writer::reset();
        $writer = writer::with_context($this->instancecontext2);
        $this->assertFalse($writer->has_any_data());
        $this->export_context_data_for_user($this->user1->id, $this->instancecontext2, 'core_grading');
        $data = $writer->get_data([get_string('gradingmethod', 'grading')]);
        $this->assertCount(1, $data->definitions);

        $firstkey = reset($data->definitions);
        $this->assertNotEmpty($firstkey->name);
        $this->assertEquals('test_method', $firstkey->method);
        $this->assertEquals(transform::datetime($now), $firstkey->timecreated);
        $this->assertEquals($this->user1->id, $firstkey->usercreated);
        $this->assertEquals($defnameprefix.'2', $firstkey->name);

        // Validate exported data: instance1 - user2 has NO data.
        $this->setUser($this->user2);
        writer::reset();
        $writer = writer::with_context($this->instancecontext1);
        $this->assertFalse($writer->has_any_data());
        $this->export_context_data_for_user($this->user2->id, $this->instancecontext1, 'core_grading');
        $data = $writer->get_data([get_string('gradingmethod', 'grading')]);
        $this->assertEmpty($data);

        // Validate exported data: instance2 - user2 has data (user has modified it).
        $this->setUser($this->user2);
        writer::reset();
        $writer = writer::with_context($this->instancecontext2);
        $this->assertFalse($writer->has_any_data());
        $this->export_context_data_for_user($this->user2->id, $this->instancecontext2, 'core_grading');
        $data = $writer->get_data([get_string('gradingmethod', 'grading')]);
        $this->assertCount(1, $data->definitions);
    }

    /**
     * Test for provider::delete_data_for_all_users_in_context().
     */
    public function test_delete_data_for_all_users_in_context() {
        global $DB;

        $this->resetAfterTest();
        $this->grading_setup_test_scenario_data();

        // Before deletion, we should have 2 grading_definitions.
        $this->assertCount(2, $DB->get_records('grading_definitions'));

        // Delete data.
        provider::delete_data_for_all_users_in_context($this->instancecontext0);
        provider::delete_data_for_all_users_in_context($this->instancecontext1);
        provider::delete_data_for_all_users_in_context($this->instancecontext2);

        // Before deletion, we should have same grading_definitions (nothing was deleted).
        $this->assertCount(2, $DB->get_records('grading_definitions'));
    }

    /**
     * Test for provider::delete_data_for_user().
     */
    public function test_delete_data_for_user() {
        global $DB;

        $this->resetAfterTest();
        $this->grading_setup_test_scenario_data();

        // Before deletion, we should have 2 grading_definitions.
        $this->assertCount(2, $DB->get_records('grading_definitions'));

        // Delete data for $user0.
        $contextlist = provider::get_contexts_for_userid($this->user0->id);
        $approvedcontextlist = new approved_contextlist(
            $this->user0,
            'core_grading',
            $contextlist->get_contextids()
        );
        provider::delete_data_for_user($approvedcontextlist);

        // Delete data for $user1.
        $contextlist = provider::get_contexts_for_userid($this->user1->id);
        $approvedcontextlist = new approved_contextlist(
            $this->user1,
            'core_grading',
            $contextlist->get_contextids()
        );
        provider::delete_data_for_user($approvedcontextlist);

        // Delete data for $user2.
        $contextlist = provider::get_contexts_for_userid($this->user2->id);
        $approvedcontextlist = new approved_contextlist(
            $this->user2,
            'core_grading',
            $contextlist->get_contextids()
        );
        provider::delete_data_for_user($approvedcontextlist);

        // Before deletion, we should have same grading_definitions (nothing was deleted).
        $this->assertCount(2, $DB->get_records('grading_definitions'));
    }

    /**
     * Test exporting user data relating to an item ID.
     */
    public function test_export_item_data() {
        global $DB;
        $this->resetAfterTest();
        $course = $this->getDataGenerator()->create_course();
        $module = $this->getDataGenerator()->create_module('assign', ['course' => $course]);
        $user = $this->getDataGenerator()->create_user();
        $guidegenerator = \testing_util::get_data_generator()->get_plugin_generator('gradingform_guide');

        $this->setUser($user);

        $modulecontext = \context_module::instance($module->cmid);
        $controller = $guidegenerator->get_test_guide($modulecontext);

        // In the situation of mod_assign this would be the id from assign_grades.
        $itemid = 1;
        $instance = $controller->create_instance($user->id, $itemid);
        $data = $guidegenerator->get_test_form_data(
            $controller,
            $itemid,
            5, 'This user made several mistakes.',
            10, 'This user has two pictures.'
        );

        $instance->update($data);
        $instanceid = $instance->get_data('id');

        provider::export_item_data($modulecontext, $itemid, ['Test']);
        $data = (array) writer::with_context($modulecontext)->get_data(['Test', 'Marking guide', $instance->get_data('id')]);
        $this->assertCount(2, $data);
        $this->assertEquals('This user made several mistakes.', $data['Spelling mistakes']->remark);
        $this->assertEquals(5, $data['Spelling mistakes']->score);
        $this->assertEquals('This user has two pictures.', $data['Pictures']->remark);
        $this->assertEquals(10, $data['Pictures']->score);
    }

    /**
     * Test deleting user data related to a context and item ID.
     */
    public function test_delete_instance_data() {
        global $DB;
        $this->resetAfterTest();
        $course = $this->getDataGenerator()->create_course();
        $module = $this->getDataGenerator()->create_module('assign', ['course' => $course]);
        $user = $this->getDataGenerator()->create_user();
        $guidegenerator = \testing_util::get_data_generator()->get_plugin_generator('gradingform_guide');

        $this->setUser($user);

        $modulecontext = \context_module::instance($module->cmid);
        $controller = $guidegenerator->get_test_guide($modulecontext);

        // In the situation of mod_assign this would be the id from assign_grades.
        $itemid = 1;
        $instance = $controller->create_instance($user->id, $itemid);
        $data = $guidegenerator->get_test_form_data(
            $controller,
            $itemid,
            5, 'This user made several mistakes.',
            10, 'This user has two pictures.'
        );
        $instance->update($data);

        $itemid = 2;
        $instance = $controller->create_instance($user->id, $itemid);
        $data = $guidegenerator->get_test_form_data(
            $controller,
            $itemid,
            25, 'This user made no mistakes.',
            5, 'This user has one picture.'
        );
        $instance->update($data);

        // Check how many records we have in the fillings table.
        $records = $DB->get_records('gradingform_guide_fillings');
        $this->assertCount(4, $records);
        // Let's delete one of the instances (the last one would be the easiest).
        provider::delete_instance_data($modulecontext, $itemid);
        $records = $DB->get_records('gradingform_guide_fillings');
        $this->assertCount(2, $records);
        foreach ($records as $record) {
            $this->assertNotEquals($instance->get_id(), $record->instanceid);
        }
        // This will delete all the rest of the instances for this context.
        provider::delete_instance_data($modulecontext);
        $records = $DB->get_records('gradingform_guide_fillings');
        $this->assertEmpty($records);
    }

    /**
     * Test the deletion of multiple instances at once.
     */
    public function test_delete_data_for_instances() {
        global $DB;
        $this->resetAfterTest();
        $course = $this->getDataGenerator()->create_course();
        $module = $this->getDataGenerator()->create_module('assign', ['course' => $course]);
        $user1 = $this->getDataGenerator()->create_user();
        $user2 = $this->getDataGenerator()->create_user();
        $user3 = $this->getDataGenerator()->create_user();
        $guidegenerator = \testing_util::get_data_generator()->get_plugin_generator('gradingform_guide');

        $this->setUser($user1);

        $modulecontext = \context_module::instance($module->cmid);
        $controller = $guidegenerator->get_test_guide($modulecontext);

        // In the situation of mod_assign this would be the id from assign_grades.
        $itemid1 = 1;
        $instance1 = $controller->create_instance($user1->id, $itemid1);
        $data = $guidegenerator->get_test_form_data(
            $controller,
            $itemid1,
            5, 'This user made several mistakes.',
            10, 'This user has two pictures.'
        );
        $instance1->update($data);

        $itemid2 = 2;
        $instance2 = $controller->create_instance($user2->id, $itemid2);
        $data = $guidegenerator->get_test_form_data(
            $controller,
            $itemid2,
            15, 'This user made a couple of mistakes.',
            10, 'This user has one picture.'
        );
        $instance2->update($data);

        $itemid3 = 3;
        $instance3 = $controller->create_instance($user3->id, $itemid3);
        $data = $guidegenerator->get_test_form_data(
            $controller,
            $itemid3,
            20, 'This user made one mistakes.',
            10, 'This user has one picture.'
        );
        $instance3->update($data);

        $records = $DB->get_records('gradingform_guide_fillings');
        $this->assertCount(6, $records);

        // Delete all user data for items 1 and 3.
        provider::delete_data_for_instances($modulecontext, [$itemid1, $itemid3]);

        $records = $DB->get_records('gradingform_guide_fillings');
        $this->assertCount(2, $records);
        $instanceid = $instance2->get_data('id');
        // The instance id should match for all remaining records.
        foreach ($records as $record) {
            $this->assertEquals($instanceid, $record->instanceid);
        }
    }

    /**
     * Helper function to setup the environment.
     *
     * course
     *  |
     *  +--instance0 (assignment)
     *  |   |
     *  +--instance1 (assignment)
     *  |   |
     *  |   +--grading_definition1 (created and modified by user1)
     *  |   |
     *  +--instance2 (assignment)
     *  |   |
     *  |   +--grading_definition2 (created by user1 and modified by user2)
     *
     *
     * user0 hasn't any data.
     *
     * @param string $defnameprefix
     * @param timestamp $now
     */
    protected function grading_setup_test_scenario_data($defnameprefix = null, $now = null) {
        global $DB;

        $this->user0 = $this->getDataGenerator()->create_user();
        $this->user1 = $this->getDataGenerator()->create_user();
        $this->user2 = $this->getDataGenerator()->create_user();

        // Create a course.
        $course = $this->getDataGenerator()->create_course();
        $coursecontext = \context_course::instance($course->id);

        // Create some assignment instances.
        $params = (object)array(
            'course' => $course->id,
            'name'   => 'Testing instance'
        );
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
        $instance0 = $generator->create_instance($params);
        $cm0 = get_coursemodule_from_instance('assign', $instance0->id);
        $this->instancecontext0 = \context_module::instance($cm0->id);
        $instance1 = $generator->create_instance($params);
        $cm1 = get_coursemodule_from_instance('assign', $instance1->id);
        $this->instancecontext1 = \context_module::instance($cm1->id);
        $instance2 = $generator->create_instance($params);
        $cm2 = get_coursemodule_from_instance('assign', $instance2->id);
        $this->instancecontext2 = \context_module::instance($cm2->id);

        // Create fake grading areas.
        $fakearea1 = (object)array(
            'contextid'    => $this->instancecontext1->id,
            'component'    => 'mod_assign',
            'areaname'     => 'submissions',
            'activemethod' => 'test_method'
        );
        $fakeareaid1 = $DB->insert_record('grading_areas', $fakearea1);
        $fakearea2 = clone($fakearea1);
        $fakearea2->contextid = $this->instancecontext2->id;
        $fakeareaid2 = $DB->insert_record('grading_areas', $fakearea2);

        // Create fake grading definitions.
        if (empty($now)) {
            $now = time();
        }
        if (empty($defnameprefix)) {
            $defnameprefix = 'fakename';
        }
        $fakedefinition1 = (object)array(
            'areaid'       => $fakeareaid1,
            'method'       => 'test_method',
            'name'         => $defnameprefix.'1',
            'status'       => 0,
            'timecreated'  => $now,
            'usercreated'  => $this->user1->id,
            'timemodified' => $now + 1,
            'usermodified' => $this->user1->id,
        );
        $fakedefid1 = $DB->insert_record('grading_definitions', $fakedefinition1);
        $fakedefinition2 = clone($fakedefinition1);
        $fakedefinition2->areaid = $fakeareaid2;
        $fakedefinition2->name = $defnameprefix.'2';
        $fakedefinition2->usermodified = $this->user2->id;
        $fakedefid2 = $DB->insert_record('grading_definitions', $fakedefinition2);
    }
}
