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

/**
 * Provides the {@see mod_workshop\privacy\provider_test} class.
 *
 * @package     mod_workshop
 * @category    test
 * @copyright   2018 David Mudrák <david@moodle.com>
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
namespace mod_workshop\privacy;

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

global $CFG;

use core_privacy\local\request\writer;
use core_privacy\tests\provider_testcase;

/**
 * Unit tests for the privacy API implementation.
 *
 * @copyright 2018 David Mudrák <david@moodle.com>
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
final class provider_test extends provider_testcase {

    /** @var testing_data_generator */
    protected $generator;

    /** @var mod_workshop_generator */
    protected $workshopgenerator;

    /** @var stdClass */
    protected $course1;

    /** @var stdClass */
    protected $course2;

    /** @var stdClass */
    protected $student1;

    /** @var stdClass */
    protected $student2;

    /** @var stdClass */
    protected $student3;

    /** @var stdClass */
    protected $teacher4;

    /** @var stdClass first workshop in course1 */
    protected $workshop11;

    /** @var stdClass second workshop in course1 */
    protected $workshop12;

    /** @var stdClass first workshop in course2 */
    protected $workshop21;

    /** @var int ID of the submission in workshop11 by student1 */
    protected $submission111;

    /** @var int ID of the submission in workshop12 by student1 */
    protected $submission121;

    /** @var int ID of the submission in workshop12 by student2 */
    protected $submission122;

    /** @var int ID of the submission in workshop21 by student2 */
    protected $submission212;

    /** @var int ID of the assessment of submission111 by student1 */
    protected $assessment1111;

    /** @var int ID of the assessment of submission111 by student2 */
    protected $assessment1112;

    /** @var int ID of the assessment of submission111 by student3 */
    protected $assessment1113;

    /** @var int ID of the assessment of submission121 by student2 */
    protected $assessment1212;

    /** @var int ID of the assessment of submission212 by student1 */
    protected $assessment2121;

    /**
     * Set up the test environment.
     *
     * course1
     *  |
     *  +--workshop11 (first digit matches the course, second is incremental)
     *  |   |
     *  |   +--submission111 (first two digits match the workshop, last one matches the author)
     *  |       |
     *  |       +--assessment1111 (first three digits match the submission, last one matches the reviewer)
     *  |       +--assessment1112
     *  |       +--assessment1113
     *  |
     *  +--workshop12
     *      |
     *      +--submission121
     *      |   |
     *      |   +--assessment1212
     *      |
     *      +--submission122
     *
     *  etc.
     */
    protected function setUp(): void {
        global $DB;
        parent::setUp();
        $this->resetAfterTest();
        $this->setAdminUser();

        $this->generator = $this->getDataGenerator();
        $this->workshopgenerator = $this->generator->get_plugin_generator('mod_workshop');

        $this->course1 = $this->generator->create_course();
        $this->course2 = $this->generator->create_course();

        $this->workshop11 = $this->generator->create_module('workshop', [
            'course' => $this->course1,
            'name' => 'Workshop11',
        ]);
        $DB->set_field('workshop', 'phase', 50, ['id' => $this->workshop11->id]);

        $this->workshop12 = $this->generator->create_module('workshop', ['course' => $this->course1]);
        $this->workshop21 = $this->generator->create_module('workshop', ['course' => $this->course2]);

        $this->student1 = $this->generator->create_user();
        $this->student2 = $this->generator->create_user();
        $this->student3 = $this->generator->create_user();
        $this->teacher4 = $this->generator->create_user();

        $this->submission111 = $this->workshopgenerator->create_submission($this->workshop11->id, $this->student1->id);
        $this->submission121 = $this->workshopgenerator->create_submission($this->workshop12->id, $this->student1->id,
            ['gradeoverby' => $this->teacher4->id]);
        $this->submission122 = $this->workshopgenerator->create_submission($this->workshop12->id, $this->student2->id);
        $this->submission212 = $this->workshopgenerator->create_submission($this->workshop21->id, $this->student2->id);

        $this->assessment1111 = $this->workshopgenerator->create_assessment($this->submission111, $this->student1->id, [
            'grade' => null,
        ]);
        $this->assessment1112 = $this->workshopgenerator->create_assessment($this->submission111, $this->student2->id, [
            'grade' => 92,
        ]);
        $this->assessment1113 = $this->workshopgenerator->create_assessment($this->submission111, $this->student3->id);

        $this->assessment1212 = $this->workshopgenerator->create_assessment($this->submission121, $this->student2->id, [
            'feedbackauthor' => 'This is what student 2 thinks about submission 121',
            'feedbackreviewer' => 'This is what the teacher thinks about this assessment',
        ]);

        $this->assessment2121 = $this->workshopgenerator->create_assessment($this->submission212, $this->student1->id, [
            'grade' => 68,
            'gradinggradeover' => 80,
            'gradinggradeoverby' => $this->teacher4->id,
            'feedbackauthor' => 'This is what student 1 thinks about submission 212',
            'feedbackreviewer' => 'This is what the teacher thinks about this assessment',
        ]);
    }

    /**
     * Test {@link \mod_workshop\privacy\provider::get_contexts_for_userid()} implementation.
     */
    public function test_get_contexts_for_userid(): void {

        $cm11 = get_coursemodule_from_instance('workshop', $this->workshop11->id);
        $cm12 = get_coursemodule_from_instance('workshop', $this->workshop12->id);
        $cm21 = get_coursemodule_from_instance('workshop', $this->workshop21->id);

        $context11 = \context_module::instance($cm11->id);
        $context12 = \context_module::instance($cm12->id);
        $context21 = \context_module::instance($cm21->id);

        // Student1 has data in workshop11 (author + self reviewer), workshop12 (author) and workshop21 (reviewer).
        $contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->student1->id);
        $this->assertInstanceOf(\core_privacy\local\request\contextlist::class, $contextlist);
        $this->assertEqualsCanonicalizing([$context11->id, $context12->id, $context21->id], array_values($contextlist->get_contextids()));

        // Student2 has data in workshop11 (reviewer), workshop12 (reviewer) and workshop21 (author).
        $contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->student2->id);
        $this->assertEqualsCanonicalizing([$context11->id, $context12->id, $context21->id], array_values($contextlist->get_contextids()));

        // Student3 has data in workshop11 (reviewer).
        $contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->student3->id);
        $this->assertEqualsCanonicalizing([$context11->id], array_values($contextlist->get_contextids()));

        // Teacher4 has data in workshop12 (gradeoverby) and workshop21 (gradinggradeoverby).
        $contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->teacher4->id);
        $this->assertEqualsCanonicalizing([$context21->id, $context12->id], array_values($contextlist->get_contextids()));
    }

    /**
     * Test {@link \mod_workshop\privacy\provider::get_users_in_context()} implementation.
     */
    public function test_get_users_in_context(): void {

        $cm11 = get_coursemodule_from_instance('workshop', $this->workshop11->id);
        $cm12 = get_coursemodule_from_instance('workshop', $this->workshop12->id);
        $cm21 = get_coursemodule_from_instance('workshop', $this->workshop21->id);

        $context11 = \context_module::instance($cm11->id);
        $context12 = \context_module::instance($cm12->id);
        $context21 = \context_module::instance($cm21->id);

        // Users in the workshop11.
        $userlist11 = new \core_privacy\local\request\userlist($context11, 'mod_workshop');
        \mod_workshop\privacy\provider::get_users_in_context($userlist11);
        $expected11 = [
            $this->student1->id, // Student1 has data in workshop11 (author + self reviewer).
            $this->student2->id, // Student2 has data in workshop11 (reviewer).
            $this->student3->id, // Student3 has data in workshop11 (reviewer).
        ];
        $actual11 = $userlist11->get_userids();
        $this->assertEqualsCanonicalizing($expected11, $actual11);

        // Users in the workshop12.
        $userlist12 = new \core_privacy\local\request\userlist($context12, 'mod_workshop');
        \mod_workshop\privacy\provider::get_users_in_context($userlist12);
        $expected12 = [
            $this->student1->id, // Student1 has data in workshop12 (author).
            $this->student2->id, // Student2 has data in workshop12 (reviewer).
            $this->teacher4->id, // Teacher4 has data in workshop12 (gradeoverby).
        ];
        $actual12 = $userlist12->get_userids();
        $this->assertEqualsCanonicalizing($expected12, $actual12);

        // Users in the workshop21.
        $userlist21 = new \core_privacy\local\request\userlist($context21, 'mod_workshop');
        \mod_workshop\privacy\provider::get_users_in_context($userlist21);
        $expected21 = [
            $this->student1->id, // Student1 has data in workshop21 (reviewer).
            $this->student2->id, // Student2 has data in workshop21 (author).
            $this->teacher4->id, // Teacher4 has data in workshop21 (gradinggradeoverby).
        ];
        $actual21 = $userlist21->get_userids();
        $this->assertEqualsCanonicalizing($expected21, $actual21);
    }

    /**
     * Test {@link \mod_workshop\privacy\provider::export_user_data()} implementation.
     */
    public function test_export_user_data_1(): void {

        $contextlist = new \core_privacy\local\request\approved_contextlist($this->student1, 'mod_workshop', [
            \context_module::instance($this->workshop11->cmid)->id,
            \context_module::instance($this->workshop12->cmid)->id,
        ]);

        \mod_workshop\privacy\provider::export_user_data($contextlist);

        $writer = writer::with_context(\context_module::instance($this->workshop11->cmid));

        $workshop = $writer->get_data([]);
        $this->assertEquals('Workshop11', $workshop->name);
        $this->assertObjectHasProperty('phase', $workshop);

        $mysubmission = $writer->get_data([
            get_string('mysubmission', 'mod_workshop'),
        ]);

        $mysubmissionselfassessmentwithoutgrade = $writer->get_data([
            get_string('mysubmission', 'mod_workshop'),
            get_string('assessments', 'mod_workshop'),
            $this->assessment1111,
        ]);
        $this->assertNull($mysubmissionselfassessmentwithoutgrade->grade);
        $this->assertEquals(get_string('yes'), $mysubmissionselfassessmentwithoutgrade->selfassessment);

        $mysubmissionassessmentwithgrade = $writer->get_data([
            get_string('mysubmission', 'mod_workshop'),
            get_string('assessments', 'mod_workshop'),
            $this->assessment1112,
        ]);
        $this->assertEquals(92, $mysubmissionassessmentwithgrade->grade);
        $this->assertEquals(get_string('no'), $mysubmissionassessmentwithgrade->selfassessment);

        $mysubmissionassessmentwithoutgrade = $writer->get_data([
            get_string('mysubmission', 'mod_workshop'),
            get_string('assessments', 'mod_workshop'),
            $this->assessment1113,
        ]);
        $this->assertEquals(null, $mysubmissionassessmentwithoutgrade->grade);
        $this->assertEquals(get_string('no'), $mysubmissionassessmentwithoutgrade->selfassessment);

        $myassessments = $writer->get_data([
            get_string('myassessments', 'mod_workshop'),
        ]);
        $this->assertEmpty($myassessments);
    }

    /**
     * Test {@link \mod_workshop\privacy\provider::export_user_data()} implementation.
     */
    public function test_export_user_data_2(): void {

        $contextlist = new \core_privacy\local\request\approved_contextlist($this->student2, 'mod_workshop', [
            \context_module::instance($this->workshop11->cmid)->id,
        ]);

        \mod_workshop\privacy\provider::export_user_data($contextlist);

        $writer = writer::with_context(\context_module::instance($this->workshop11->cmid));

        $assessedsubmission = $writer->get_related_data([
            get_string('myassessments', 'mod_workshop'),
            $this->assessment1112,
        ], 'submission');
        $this->assertEquals(get_string('no'), $assessedsubmission->myownsubmission);
    }

    /**
     * Test {@link \mod_workshop\privacy\provider::delete_data_for_all_users_in_context()} implementation.
     */
    public function test_delete_data_for_all_users_in_context(): void {
        global $DB;

        $this->assertTrue($DB->record_exists('workshop_submissions', ['workshopid' => $this->workshop11->id]));

        // Passing a non-module context does nothing.
        \mod_workshop\privacy\provider::delete_data_for_all_users_in_context(\context_course::instance($this->course1->id));
        $this->assertTrue($DB->record_exists('workshop_submissions', ['workshopid' => $this->workshop11->id]));

        // Passing a workshop context removes all data.
        \mod_workshop\privacy\provider::delete_data_for_all_users_in_context(\context_module::instance($this->workshop11->cmid));
        $this->assertFalse($DB->record_exists('workshop_submissions', ['workshopid' => $this->workshop11->id]));
    }

    /**
     * Test {@link \mod_workshop\privacy\provider::delete_data_for_user()} implementation.
     */
    public function test_delete_data_for_user(): void {
        global $DB;

        $student1submissions = $DB->get_records('workshop_submissions', [
            'workshopid' => $this->workshop12->id,
            'authorid' => $this->student1->id,
        ]);

        $student2submissions = $DB->get_records('workshop_submissions', [
            'workshopid' => $this->workshop12->id,
            'authorid' => $this->student2->id,
        ]);

        $this->assertNotEmpty($student1submissions);
        $this->assertNotEmpty($student2submissions);

        foreach ($student1submissions as $submission) {
            $this->assertNotEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title);
        }

        foreach ($student2submissions as $submission) {
            $this->assertNotEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title);
        }

        $contextlist = new \core_privacy\local\request\approved_contextlist($this->student1, 'mod_workshop', [
            \context_module::instance($this->workshop12->cmid)->id,
            \context_module::instance($this->workshop21->cmid)->id,
        ]);

        \mod_workshop\privacy\provider::delete_data_for_user($contextlist);

        $student1submissions = $DB->get_records('workshop_submissions', [
            'workshopid' => $this->workshop12->id,
            'authorid' => $this->student1->id,
        ]);

        $student2submissions = $DB->get_records('workshop_submissions', [
            'workshopid' => $this->workshop12->id,
            'authorid' => $this->student2->id,
        ]);

        $this->assertNotEmpty($student1submissions);
        $this->assertNotEmpty($student2submissions);

        foreach ($student1submissions as $submission) {
            $this->assertEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title);
        }

        foreach ($student2submissions as $submission) {
            $this->assertNotEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title);
        }

        $student1assessments = $DB->get_records('workshop_assessments', [
            'submissionid' => $this->submission212,
            'reviewerid' => $this->student1->id,
        ]);
        $this->assertNotEmpty($student1assessments);

        foreach ($student1assessments as $assessment) {
            // In Moodle, feedback is seen to belong to the recipient user.
            $this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackauthor);
            $this->assertEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackreviewer);
            // We delete what we can without affecting others' grades.
            $this->assertEquals(68, $assessment->grade);
        }

        $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($student1submissions));
        $this->assertNotEmpty($assessments);

        foreach ($assessments as $assessment) {
            if ($assessment->reviewerid == $this->student1->id) {
                $this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackauthor);
                $this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackreviewer);

            } else {
                $this->assertEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackauthor);
                $this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackreviewer);
            }
        }
    }

    /**
     * Test {@link \mod_workshop\privacy\provider::delete_data_for_users()} implementation.
     */
    public function test_delete_data_for_users(): void {
        global $DB;

        // Student1 has submissions in two workshops.
        $this->assertFalse($this->is_submission_erased($this->submission111));
        $this->assertFalse($this->is_submission_erased($this->submission121));

        // Student1 has self-assessed one their submission.
        $this->assertFalse($this->is_given_assessment_erased($this->assessment1111));
        $this->assertFalse($this->is_received_assessment_erased($this->assessment1111));

        // Student2 and student3 peer-assessed student1's submission.
        $this->assertFalse($this->is_given_assessment_erased($this->assessment1112));
        $this->assertFalse($this->is_given_assessment_erased($this->assessment1113));

        // Delete data owned by student1 and student3 in the workshop11.

        $context11 = \context_module::instance($this->workshop11->cmid);

        $approveduserlist = new \core_privacy\local\request\approved_userlist($context11, 'mod_workshop', [
            $this->student1->id,
            $this->student3->id,
        ]);
        \mod_workshop\privacy\provider::delete_data_for_users($approveduserlist);

        // Student1's submission is erased in workshop11 but not in the other workshop12.
        $this->assertTrue($this->is_submission_erased($this->submission111));
        $this->assertFalse($this->is_submission_erased($this->submission121));

        // Student1's self-assessment is erased.
        $this->assertTrue($this->is_given_assessment_erased($this->assessment1111));
        $this->assertTrue($this->is_received_assessment_erased($this->assessment1111));

        // Student1's received peer-assessments are also erased because they are "owned" by the recipient of the assessment.
        $this->assertTrue($this->is_received_assessment_erased($this->assessment1112));
        $this->assertTrue($this->is_received_assessment_erased($this->assessment1113));

        // Student2's owned data in the given assessment are not erased.
        $this->assertFalse($this->is_given_assessment_erased($this->assessment1112));

        // Student3's owned data in the given assessment were erased because she/he was in the userlist.
        $this->assertTrue($this->is_given_assessment_erased($this->assessment1113));

        // Personal data in other contexts are not affected.
        $this->assertFalse($this->is_submission_erased($this->submission121));
        $this->assertFalse($this->is_given_assessment_erased($this->assessment2121));
        $this->assertFalse($this->is_received_assessment_erased($this->assessment2121));
    }

    /**
     * Check if the given submission has the author's personal data erased.
     *
     * @param int $submissionid Identifier of the submission.
     * @return boolean
     */
    protected function is_submission_erased(int $submissionid) {
        global $DB;

        $submission = $DB->get_record('workshop_submissions', ['id' => $submissionid], 'id, title, content', MUST_EXIST);

        $titledeleted = $submission->title === get_string('privacy:request:delete:title', 'mod_workshop');
        $contentdeleted = $submission->content === get_string('privacy:request:delete:content', 'mod_workshop');

        if ($titledeleted && $contentdeleted) {
            return true;

        } else {
            return false;
        }
    }

    /**
     * Check is the received assessment has recipient's (author's) personal data erased.
     *
     * @param int $assessmentid Identifier of the assessment.
     * @return boolean
     */
    protected function is_received_assessment_erased(int $assessmentid) {
        global $DB;

        $assessment = $DB->get_record('workshop_assessments', ['id' => $assessmentid], 'id, feedbackauthor', MUST_EXIST);

        if ($assessment->feedbackauthor === get_string('privacy:request:delete:content', 'mod_workshop')) {
            return true;

        } else {
            return false;
        }
    }

    /**
     * Check is the given assessment has reviewer's personal data erased.
     *
     * @param int $assessmentid Identifier of the assessment.
     * @return boolean
     */
    protected function is_given_assessment_erased(int $assessmentid) {
        global $DB;

        $assessment = $DB->get_record('workshop_assessments', ['id' => $assessmentid], 'id, feedbackreviewer', MUST_EXIST);

        if ($assessment->feedbackreviewer === get_string('privacy:request:delete:content', 'mod_workshop')) {
            return true;

        } else {
            return false;
        }
    }
}
