<?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 customfield_number;

use context_module;
use customfield_number\local\numberproviders\nofactivities;
use customfield_number\task\recalculate;

/**
 * Testing event observers
 *
 * @covers     \customfield_number\observer
 * @covers     \customfield_number\task\recalculate
 * @package    customfield_number
 * @category   test
 * @copyright  Marina Glancy
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
final class observer_test extends \advanced_testcase {

    /**
     * Create a number custom field
     *
     * @param array $configdata
     * @return \customfield_number\field_controller
     */
    protected function create_number_custom_field(array $configdata): field_controller {
        /** @var \core_customfield_generator $generator */
        $generator = $this->getDataGenerator()->get_plugin_generator('core_customfield');

        // Create a category and field.
        $category = $generator->create_category();
        $field = $generator->create_field([
            'categoryid' => $category->get('id'),
            'type' => 'number',
            'configdata' => $configdata,
        ]);
        return $field;
    }

    /**
     * Helper function that checks if the recalculate ad-hoc task is scheduled
     *
     * @param bool $mustbescheduled - when false checks that the adhoc task is NOT scheduled
     * @return void
     */
    protected function ensure_number_adhoc_task_is_scheduled(bool $mustbescheduled): void {
        $tasks = array_filter(
            \core\task\manager::get_candidate_adhoc_tasks(time(), 1200, null),
            fn($task) => $task->classname === '\\' . recalculate::class
        );
        if ($mustbescheduled && empty($tasks)) {
            $this->fail('Recalculate ad-hoc task is not scheduled.');
        } else if (!$mustbescheduled && !empty($tasks)) {
            $this->fail('Recalculate ad-hoc task is scheduled when it is not expected.');
        }
    }

    /**
     * Test for observer for field_created event
     */
    public function test_field_created(): void {
        global $DB;

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

        $course1 = $this->getDataGenerator()->create_course();
        $course2 = $this->getDataGenerator()->create_course();
        $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
        $assign1 = $assigngenerator->create_instance(['course' => $course1->id, 'visible' => 1]);
        $assigngenerator->create_instance(['course' => $course1->id, 'visible' => 1]);

        // Create a number field with a provider.
        $field = $this->create_number_custom_field(['fieldtype' => nofactivities::class, 'activitytypes' => ['assign', 'forum']]);

        // Execute scheduled ad-hoc tasks and it will populate the data for the course.
        $this->ensure_number_adhoc_task_is_scheduled(true);
        $this->run_all_adhoc_tasks();

        $alldata = $DB->get_records_menu('customfield_data',
            ['fieldid' => $field->get('id')], 'instanceid', 'instanceid, decvalue');
        $this->assertEquals([$course1->id => 2], $alldata);

        // Creating another field type does not schedule tasks.
        $this->ensure_number_adhoc_task_is_scheduled(false);
        $this->getDataGenerator()->get_plugin_generator('core_customfield')->create_field((object)[
            'categoryid' => $field->get_category()->get('id'),
            'type' => 'textarea',
        ]);
        $this->ensure_number_adhoc_task_is_scheduled(false);
    }

    /**
     * Test for observer for field_updated event
     */
    public function test_field_updated(): void {
        global $DB;

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

        $course1 = $this->getDataGenerator()->create_course();
        $course2 = $this->getDataGenerator()->create_course();
        /** @var \mod_assign_generator $assigngenerator */
        $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
        $assign1 = $assigngenerator->create_instance(['course' => $course1->id, 'visible' => 1]);
        $assigngenerator->create_instance(['course' => $course1->id, 'visible' => 1]);

        // Create a simple number field.
        $field = $this->create_number_custom_field([]);

        // There is no data for this field yet.
        $this->ensure_number_adhoc_task_is_scheduled(true);
        $this->run_all_adhoc_tasks();
        $this->assertEmpty($DB->get_records('customfield_data', ['fieldid' => $field->get('id')]));

        // Update this field to use nofactivities as provider.
        $params = ['fieldtype' => nofactivities::class, 'activitytypes' => ['assign', 'forum']];
        \core_customfield\api::save_field_configuration($field, (object)['configdata' => $params]);

        // Now an ad-hoc task is scheduled and the data is populated.
        $this->ensure_number_adhoc_task_is_scheduled(true);
        $this->run_all_adhoc_tasks();

        $alldata = $DB->get_records_menu(
            'customfield_data',
            ['fieldid' => $field->get('id')],
            'instanceid',
            'instanceid, decvalue'
        );
        $this->assertEquals([$course1->id => 2], $alldata);
    }

    /**
     * Test for observer for course_module_created, course_module_updated and course_module_deleted events
     */
    public function test_course_module_events(): void {
        global $DB;

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

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

        // Create a number field with a provider.
        $field = $this->create_number_custom_field(['fieldtype' => nofactivities::class, 'activitytypes' => ['assign', 'forum']]);

        // There is no data for this field yet.
        $this->ensure_number_adhoc_task_is_scheduled(true);
        $this->run_all_adhoc_tasks();
        $this->assertEmpty($DB->get_records('customfield_data', ['fieldid' => $field->get('id')]));

        // Create modules.
        $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
        $assign1 = $assigngenerator->create_instance(['course' => $course1->id, 'visible' => 1]);
        $assign2 = $assigngenerator->create_instance(['course' => $course1->id, 'visible' => 0]);
        $assigngenerator->create_instance(['course' => $course1->id, 'visible' => 1]);

        // Execute scheduled ad-hoc tasks and it will populate the data for the course.
        $this->ensure_number_adhoc_task_is_scheduled(true);
        $this->run_all_adhoc_tasks();

        $alldata = $DB->get_records_menu('customfield_data',
            ['fieldid' => $field->get('id')], 'instanceid', 'instanceid, decvalue');
        $this->assertEquals([$course1->id => 2], $alldata);

        // Update visibility of one module.
        set_coursemodule_visible($assign2->cmid, 1);
        [$course, $cm] = get_course_and_cm_from_cmid($assign2->cmid);
        \core\event\course_module_updated::create_from_cm($cm, context_module::instance($assign2->cmid))->trigger();
        $this->ensure_number_adhoc_task_is_scheduled(true);
        $this->run_all_adhoc_tasks();

        $alldata = $DB->get_records_menu('customfield_data',
            ['fieldid' => $field->get('id')], 'instanceid', 'instanceid, decvalue');
        $this->assertEquals([$course1->id => 3], $alldata);

        // Delete one module.
        course_delete_module($assign1->cmid);

        // Execute scheduled ad-hoc tasks and it will update the data for the course.
        $this->ensure_number_adhoc_task_is_scheduled(true);
        $this->run_all_adhoc_tasks();

        $alldata = $DB->get_records_menu('customfield_data',
            ['fieldid' => $field->get('id')], 'instanceid', 'instanceid, decvalue');
        $this->assertEquals([$course1->id => 2], $alldata);
    }

    /**
     * Creating, updating and deleting modules when there are no 'nofactivities' custom fields does not schedule the ad-hoc task
     *
     * @return void
     */
    public function test_course_module_events_without_custom_fields(): void {
        global $DB;

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

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

        // Create a number field without a provider.
        $field = $this->create_number_custom_field([]);

        // Initial ad-hoc task was scheduled.
        $this->ensure_number_adhoc_task_is_scheduled(true);
        $this->run_all_adhoc_tasks();

        // Create modules.
        $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
        $assign1 = $assigngenerator->create_instance(['course' => $course1->id, 'visible' => 1]);
        $assign2 = $assigngenerator->create_instance(['course' => $course1->id, 'visible' => 0]);
        $assigngenerator->create_instance(['course' => $course1->id, 'visible' => 1]);

        $this->ensure_number_adhoc_task_is_scheduled(false);

        // Update visibility of one module.
        set_coursemodule_visible($assign2->cmid, 1);
        [$course, $cm] = get_course_and_cm_from_cmid($assign2->cmid);
        \core\event\course_module_updated::create_from_cm($cm, context_module::instance($assign2->cmid))->trigger();
        $this->ensure_number_adhoc_task_is_scheduled(false);

        // Delete one module.
        course_delete_module($assign1->cmid);
        $this->ensure_number_adhoc_task_is_scheduled(false);
    }
}
