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

/**
 * Handle the course backup.
 *
 * @package    tool_opencast
 * @copyright  2025 Berthold Bußkamp, ssystems GmbH <bbusskamp@ssystems.de>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

use tool_opencast\local\settings_api;
use tool_opencast\local\apibridge;
use tool_opencast\local\event;
use tool_opencast\local\notifications;
use tool_opencast\local\importvideosmanager;


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

global $CFG, $DB;

require_once($CFG->dirroot . '/backup/moodle2/restore_tool_plugin.class.php');

/**
 * Class restore_tool_opencast_plugin
 */
class restore_tool_opencast_plugin extends restore_tool_plugin {

    /**
     * @var array Series list.
     */
    protected $series = [];
    /**
     * @var string Import mode.
     */
    protected $importmode;
    /**
     * @var array Missing event ids.
     */
    protected $missingeventids = [];
    /**
     * @var array Missing import mapping event ids.
     */
    protected $missingimportmappingeventids = [];
    /**
     * @var array Missing import mapping series ids.
     */
    protected $missingimportmappingseriesids = [];
    /**
     * @var array Backup event ids.
     */
    protected $backupeventids = [];
    /**
     * @var string Restore unique identifier.
     */
    protected $restoreuniqueid;
    /**
     * @var int Source course id.
     */
    protected $sourcecourseid;
    /**
     * @var array List of Series with ACL changed.
     */
    protected $aclchanged = [];
    /**
     * @var array List of instance ids to skip, because they are missing on the restore system.
     */
    protected $instanceidskip = [];


    /**
     * Returns plugin strucutre for the restore.
     *
     * @return string
     */
    protected function define_course_plugin_structure() {

        global $USER;

        $paths = [];

        // Get instace ids.
        $ocinstances = settings_api::get_ocinstances();

        // Get course id.
        $contextid = $this->task->get_contextid();
        $context = \core\context::instance_by_id($contextid);
        $courseid = $context->instanceid;

        // Generate restore unique identifier,
        // to keep track of restore session in later stages e.g. module mapping and repair.
        $this->restoreuniqueid = uniqid('oc_restore_' . $courseid);

        $paths[] = new restore_path_element('site', $this->connectionpoint->get_path() . '/site');

        // Processing events, grouped by main opencast, in order to get series as well.
        $paths[] = new restore_path_element('opencast', $this->connectionpoint->get_path()  . '/opencast', true);
        $paths[] = new restore_path_element('events', $this->connectionpoint->get_path() . '/opencast/events');
        $paths[] = new restore_path_element('event', $this->connectionpoint->get_path() . '/opencast/events/event');

        // Adding import property here, to access series.
        $paths[] = new restore_path_element('import', $this->connectionpoint->get_path() . '/opencast/import');
        $paths[] = new restore_path_element('series', $this->connectionpoint->get_path() . '/opencast/import/series');

        return $paths;
    }

    /**
     * Processes the Opencast data during course restore.
     *
     * Handles the restoration of Opencast series and events for each Opencast instance,
     * depending on the configured import mode (ACL change or duplication).
     * Updates internal state for series, events, and mappings, and schedules duplication tasks if needed.
     *
     * @param array $data The Opencast data from the backup file.
     * @return void
     */
    public function process_opencast($data) {

        global $USER;

        $data = (object) $data;

        $paths = [];

        // Get instace ids.
        $ocinstances = settings_api::get_ocinstances();

        // Get course id.
        $contextid = $this->task->get_contextid();
        $context = \core\context::instance_by_id($contextid);
        $courseid = $context->instanceid;

        // Handle each Opencast instance.
        foreach ($ocinstances as $ocinstance) {
            $ocinstanceid = $ocinstance->id;

            // Check against skip list.
            if (in_array($ocinstanceid, $this->instanceidskip)) {
                // Skip instance id, to avoid restoring into wrong instance.
                continue;
            }

            // Get apibridge instance.
            $apibridge = apibridge::get_instance($ocinstanceid);

            // Get the import mode to decide the way of importing opencast videos.
            $importmode = get_config('tool_opencast', 'importmode_' . $ocinstanceid);
            $this->importmode = $importmode;

            // If ACL Change is the mode.
            if ($importmode == 'acl') {

                // Collect sourcecourseid for further processing.
                $this->sourcecourseid = $data->import[0]["sourcecourseid"];

                // First level checker.
                // Exit when the course by any chance wanted to restore itself.
                if (!empty($this->sourcecourseid) && $courseid == $this->sourcecourseid) {
                    return;
                }

                // Get apibridge instance, to ensure series validity and edit series mapping.
                $apibridge = apibridge::get_instance($ocinstanceid);

                if (isset($data->import[0]['series'][0])) {
                    foreach ($data->import[0]['series'] as $series) {
                        $seriesid = $series['seriesid'];

                        // Second level checker.
                        // Exit when there is no original series, or the series is invalid.
                        if (empty($seriesid) || !$apibridge->ensure_series_is_valid($seriesid)) {
                            continue;
                        }

                        // Collect series id for notifications.
                        $this->series[$ocinstanceid] = $seriesid;

                        // Assign Seriesid to new course and change ACL.
                        $this->aclchanged[] = $apibridge->import_series_to_course_with_acl_change($courseid, $seriesid, $USER->id);
                    }
                }

            } else if ($importmode == 'duplication') {

                // Get series id.
                $seriesid = $apibridge->get_stored_seriesid($courseid, true, $USER->id);

                // If seriesid does not exist, we create one.
                if (!$seriesid) {
                    // Make sure to create using another method.
                    $seriesid = $apibridge->create_course_series($courseid, null, $USER->id);
                }
                $this->series[$ocinstanceid] = $seriesid;

                // Check if all required information is available.
                if (empty($this->series) || !isset($data->import) || !isset($data->events) ||
                    empty($data->import[0]['series']) || empty($data->events['event'])) {
                    // Nothing to do here, as the data is not enough.
                    return;
                }

                // Proceed with the backedup series, to save the mapping and repair the modules.
                foreach ($data->import[0]['series'] as $series) {
                    // Skip when the series is not from the current instance.
                    if ($series['instanceid'] != $ocinstanceid) {
                        continue;
                    }
                    $seriesid = $series['seriesid'] ?? null;
                    // Skip when there is no original series, or the series is invalid.
                    if (empty($seriesid) || !$apibridge->ensure_series_is_valid($seriesid)) {
                        continue;
                    }

                    // Record series mapping for module fix.
                    $issaved = importvideosmanager::save_series_import_mapping_record(
                        $ocinstanceid,
                        $courseid,
                        $seriesid,
                        $this->restoreuniqueid
                    );
                    if (!$issaved) {
                        $this->missingimportmappingseriesids[] = $seriesid;
                    }
                }

                foreach ($data->events['event'] as $event) {

                    // Skip when the event is not from the current instance.
                    if ($event['instanceid'] != $ocinstanceid) {
                        continue;
                    }
                    $eventid = $event['eventid'] ?? null;
                    $this->backupeventids[] = $eventid;

                    // Only duplicate, when the event exists in opencast.
                    if (!$apibridge->get_already_existing_event([$eventid])) {
                        $this->missingeventids[] = $eventid;
                    } else {
                        // Check for and record the module mappings.
                        $issaved = importvideosmanager::save_episode_import_mapping_record(
                            $ocinstanceid,
                            $courseid,
                            $eventid,
                            $this->restoreuniqueid
                        );
                        if (!$issaved) {
                            $this->missingimportmappingeventids[] = $eventid;
                        }

                        // Add the duplication task.
                        event::create_duplication_task(
                            $ocinstanceid,
                            $courseid,
                            $this->series[$ocinstanceid],
                            $eventid,
                            false,
                            null,
                            $this->restoreuniqueid
                        );
                    }
                }

            }

        }

    }

    /**
     * Process the oc instance information.
     *
     * @param array $data
     */
    public function process_site($data) {

        // Verify if ocinstance exists. Skip if not.
        $ocinstanceid = $data['ocinstanceid'];
        $apiurl = settings_api::get_apiurl($ocinstanceid);
        if (!$apiurl) {
            echo('No apiurl found for instanceid: ' . $ocinstanceid . ' Skipping this instance while restoring.' . PHP_EOL);
            $this->instanceidskip[] = $ocinstanceid;
        } else if ($apiurl != $data['apiurl']) {
            // Skip instance id, to avoid restoring into wrong instance.
            $this->instanceidskip[] = $ocinstanceid;
            echo('Wrong apiurl found for instanceid: ' . $ocinstanceid . ' Skipping this instance while restoring.' . PHP_EOL);
        }

    }

    /**
     * Process the series information.
     */
    public function after_restore_course() {
        global $DB;

        // Get instace ids.
        $ocinstances = settings_api::get_ocinstances();

        // Get course id.
        $contextid = $this->task->get_contextid();
        $context = \core\context::instance_by_id($contextid);
        $courseid = $context->instanceid;

        // Import mode is not defined.
        if (!$this->importmode) {
            notifications::notify_failed_importmode($courseid);
            return;
        }

        if ($this->importmode == 'duplication') {
            // None of the backupeventids are used for starting a workflow.
            if (!$this->series) {
                notifications::notify_failed_course_series($courseid, $this->backupeventids);
                return;
            }

            // A course series is created, but some events are not found on opencast server.
            if ($this->missingeventids) {
                notifications::notify_missing_events($courseid, $this->missingeventids);
            }

            // Notify those series that were unable to have an import mapping record.
            if (!empty($this->missingimportmappingseriesids)) {
                notifications::notify_missing_import_mapping($courseid, $this->missingimportmappingseriesids, 'series');
            }

            // Notify those events that were unable to have an import mapping record.
            if (!empty($this->missingimportmappingeventids)) {
                notifications::notify_missing_import_mapping($courseid, $this->missingimportmappingeventids, 'events');
            }

            // Set the completion for mapping.
            $report = importvideosmanager::set_import_mapping_completion_status($this->restoreuniqueid);
            // If it is report has values, that means there were failures and we report them.
            if (is_array($report)) {
                notifications::notify_incompleted_import_mapping_records($courseid, $report);
            }

            // Handle each Opencast instance.
            foreach ($ocinstances as $ocinstance) {

                $ocinstanceid = $ocinstance->id;

                // Check against skip list.
                if (in_array($ocinstanceid, $this->instanceidskip)) {
                    // Skip instance id, to avoid restoring into wrong instance.
                    continue;
                }

                $importmode = get_config('tool_opencast', 'importmode_' . $ocinstanceid);

                if ($importmode == 'duplication') {

                    // After all, we proceed to fix the series modules,
                    // because they should not wait for the duplicate workflow to finish!
                    importvideosmanager::fix_imported_series_modules_in_new_course(
                        $ocinstanceid,
                        $courseid,
                        $this->series[$ocinstanceid],
                        $this->restoreuniqueid
                    );
                }
            }
        } else if ($this->importmode == 'acl') {
            // The required data or the conditions to perform ACL change were missing.
            if (!$this->sourcecourseid) {
                notifications::notify_missing_sourcecourseid($courseid);
                return;
            }

            if (!$this->series) {
                notifications::notify_missing_seriesid($courseid);
                return;
            }
            // The ACL change import process is not successful.
            foreach ($this->aclchanged as $aclchange) {
                if ($aclchange->error == 1) {
                    if (!$aclchange->seriesaclchange) {
                        notifications::notify_failed_series_acl_change($courseid, $this->sourcecourseid, $aclchange->seriesid);
                        return;
                    }

                    if (!$aclchange->eventsaclchange && count($aclchange->eventsaclchange->failed) > 0) {
                        notifications::notify_failed_events_acl_change($courseid, $this->sourcecourseid,
                            $aclchange->eventsaclchange->failed);
                        return;
                    }

                    if (!$aclchange->seriesmapped) {
                        notifications::notify_failed_series_mapping($courseid, $this->sourcecourseid, $aclchange->seriesid);
                    }
                }
            }
        }

    }

}
