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

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

use quizaccess_quilgo\plasmapi;
use quizaccess_quilgo\local\report\report_csv;

require_once($CFG->libdir.'/externallib.php');
require_once($CFG->dirroot. '/mod/quiz/accessrule/quilgo/lib.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');

/**
 * External services used for AJAX call
 *
 * @package     quizaccess_quilgo
 * @copyright   2023 Native Platform Ltd <hello@quilgo.com>
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class quizaccess_quilgo_external extends external_api {

    /**
     * Report generated failed code
     */
    const REPORT_GENERATED_FAILED = 0;
    /**
     * Report generated success code
     */
    const REPORT_GENERATED_SUCCESS = 1;
    /**
     * Report generated not ready code
     */
    const REPORT_GENERATED_NOTREADY = 2;

    /**
     * Define parameters when fetch report
     * @return mixed
     */
    public static function fetch_report_parameters() {
        return new external_function_parameters([
            'attemptid' => new external_value(PARAM_INT, 'Attempt id'),
            'cmid' => new external_value(PARAM_INT, 'Course module id'),
        ]);
    }

    /**
     * Define returns structure after fetch report
     * @return mixed
     */
    public static function fetch_report_returns() {
        return new external_single_structure([
            'reportGeneratedCode' => new external_value(PARAM_TEXT, 'Report generated'),
            'expiresIn' => new external_value(PARAM_INT, 'Report images expires in'),
            'uuid' => new external_value(PARAM_TEXT, 'Report UUID'),
            'stat' => new external_single_structure(
                [
                    'framesTotal' => new external_value(PARAM_INT, 'Frames total'),
                    'facesPresence' => new external_value(PARAM_NUMBER, 'Face presense total'),
                    'avgFacesPerFrame' => new external_value(PARAM_NUMBER, 'Average faces per frame'),
                    'maxFacesPerFrame' => new external_value(PARAM_NUMBER, 'Max faces per frame'),
                    'pageUnfocusedCount' => new external_value(PARAM_INT, 'Page unfocused total'),
                ],
                'Statistic section',
                VALUE_OPTIONAL,
                null,
            ),
            'isStatDelivered' => new external_value(PARAM_BOOL, 'Stats delivered status'),
            'isSnapshotsLimitReached' => new external_value(PARAM_BOOL, 'Snapshots limit reached status'),
            'isScreenshotsLimitReached' => new external_value(PARAM_BOOL, 'Screenshots limit reached status'),
            'imageExpirationSeconds' => new external_value(PARAM_INT, 'Image expiration'),
            'snapshots' => new external_multiple_structure(
                new external_single_structure([
                    'id' => new external_value(PARAM_ALPHANUM, 'Snapshot id'),
                    'createdAt' => new external_value(PARAM_TEXT, 'Snapshot created at'),
                    'url' => new external_value(PARAM_URL, 'Snapshot url'),
                    'thumbnail' => new external_value(PARAM_URL, 'Snapshot thumbnail url'),
                    'facesAmount' => new external_value(PARAM_INT, 'Faces amount inside snapshot'),
                ]),
                'Snapshots content',
                VALUE_OPTIONAL,
                null,
            ),
            'screenshots' => new external_multiple_structure(
                new external_single_structure([
                    'id' => new external_value(PARAM_ALPHANUM, 'Screenshot id'),
                    'createdAt' => new external_value(PARAM_TEXT, 'Screenshot created at'),
                    'url' => new external_value(PARAM_URL, 'Screenshot url'),
                    'thumbnail' => new external_value(PARAM_URL, 'Screenshot thumbnail url'),
                    'isPageUnfocused' => new external_value(PARAM_BOOL, 'Page focused status inside screenshot'),
                ]),
                'Screenshots content',
                VALUE_OPTIONAL,
                null,
            ),
            'cameraEnabled' => new external_value(PARAM_INT, 'Report camera tracking enabled status'),
            'screenEnabled' => new external_value(PARAM_INT, 'Report screen tracking enabled status'),
            'isUsedMultipleScreens' => new external_value(PARAM_BOOL, 'Flag for used multiple screens or not'),
            'patternsDetected' => new external_value(PARAM_INT, 'How many patterns detected'),
            'quizSettingsUrl' => new external_value(PARAM_URL, 'Quiz settings url'),
            'questionsUrl' => new external_value(PARAM_RAW, 'Questions embeded page url'),
        ]);
    }

    /**
     * Handle fetch report
     * @param int $attemptid Attempt id of the report
     * @param int $cmid Course module id of the report
     * @return mixed
     */
    public static function fetch_report($attemptid, $cmid) {
        global $DB, $PAGE;

        $params = ['attemptid' => $attemptid, 'cmid' => $cmid];
        self::validate_parameters(self::fetch_report_parameters(), $params);

        $context = context_module::instance($cmid, MUST_EXIST);
        require_capability('quizaccess/quilgo:fetchreport', $context);

        $responsestat = new stdClass();
        $responsestat->framesTotal = 0;
        $responsestat->facesPresence = 0;
        $responsestat->avgFacesPerFrame = 0;
        $responsestat->maxFacesPerFrame = 0;
        $responsestat->pageUnfocusedCount = 0;

        $response = new stdClass();
        $response->reportGeneratedCode = self::REPORT_GENERATED_FAILED;
        $response->expiresIn = 0;
        $response->uuid = null;
        $response->stat = $responsestat;
        $response->isStatDelivered = false;
        $response->isSnapshotsLimitReached = false;
        $response->isScreenshotsLimitReached = false;
        $response->imageExpirationSeconds = 0;
        $response->snapshots = [];
        $response->screenshots = [];
        $response->cameraEnabled = 0;
        $response->screenEnabled = 0;
        $response->isUsedMultipleScreens = false;
        $response->patternsDetected = 0;
        $response->quizSettingsUrl = "";
        $response->questionsUrl = "";

        $attemptsession = quizaccess_quilgo_get_attempt_session($attemptid);
        if (empty($attemptsession)) {
            return $response;
        }

        $plasmapi = new plasmapi();
        $report = $plasmapi->fetch_report($attemptsession->plasmsessionid);
        if (empty($report)) {
            return $response;
        }

        $quizsettingsurl = new moodle_url('/course/modedit.php', ['update' => $cmid]);
        $response->quizSettingsUrl = $quizsettingsurl->out();

        $attempt = quizaccess_quilgo_get_attempt($attemptid);

        // Now we will treat Preview Attempt RESOURCE_NOT_FOUND as report not ready yet.
        // Because current condition we not attach webhook to plugin.
        // So we cannot store the generated stat from Plasm.
        if (!empty($report->error) && $report->error->code === "RESOURCE_NOT_FOUND") {
            if (!empty($attempt) && $attempt->preview == 1) {
                $response->reportGeneratedCode = self::REPORT_GENERATED_NOTREADY;
            }

            return $response;
        }

        if (!empty($attempt)) {
            $response->expiresIn = quizaccess_quilgo_build_report_expires($attempt->timefinish);
        }

        $response->reportGeneratedCode = self::REPORT_GENERATED_SUCCESS;
        $response->uuid = $report->uuid;
        $response->isStatDelivered = $report->isStatDelivered;
        $response->isSnapshotsLimitReached = $report->isSnapshotsLimitReached;
        $response->isScreenshotsLimitReached = $report->isScreenshotsLimitReached;
        $response->imageExpirationSeconds = $report->imageExpirationSeconds;
        $response->cameraEnabled = $attemptsession->camera_enabled;
        $response->screenEnabled = $attemptsession->screen_enabled;

        if (!empty($report->stat)) {
            $reportstat = $report->stat;

            $stat = new stdClass();
            $stat->framesTotal = $reportstat->framesTotal ?? null;
            $stat->facesPresence = $reportstat->facesPresence ?? null;
            $stat->avgFacesPerFrame = $reportstat->avgFacesPerFrame ?? null;
            $stat->maxFacesPerFrame = $reportstat->maxFacesPerFrame ?? null;
            $stat->pageUnfocusedCount = $reportstat->pageUnfocusedCount ?? null;

            $response->stat = $stat;

            $patterns = $reportstat->patterns ?? [];
            $multiplescreenspattern = array_filter($patterns, function($pattern) {
                if ($pattern->type == QUIZACCESS_QUILGO_PATTERN_TYPE_MULTIPLE_SCREENS) {
                    return true;
                }

                return false;
            });
            $response->isUsedMultipleScreens = count($multiplescreenspattern) > 0;

            $questionpatterns = array_filter($patterns, function($pattern) {
                if ($pattern->type != QUIZACCESS_QUILGO_PATTERN_TYPE_MULTIPLE_SCREENS) {
                    return true;
                }

                return false;
            });

            $patternperquestion = [];
            foreach ($questionpatterns as $questionpattern) {
                $questionpatternindex = array_search($questionpattern->questionId, array_column($patternperquestion, 'questionId'));
                if ($questionpatternindex === false) {
                    $patternperquestion[] = $questionpattern;
                } else {
                    $existingpattern = $patternperquestion[$questionpatternindex];
                    if (
                        $existingpattern->type === QUIZACCESS_QUILGO_PATTERN_TYPE_COPY_CHANGE &&
                        $questionpattern->type === QUIZACCESS_QUILGO_PATTERN_TYPE_COPY_PASTE
                    ) {
                        $patternperquestion[$questionpatternindex] = $questionpattern;
                    }
                }
            }
            $response->patternsDetected = count($patternperquestion);

            // We will build patterns query with the format question-id-1#event|question-id-2#event
            // This patterns query is for showing patterns on questions iframe page.
            $patternsquery = [];
            foreach ($patternperquestion as $pattern) {
                $patternsquery[] = $pattern->questionId . "#" . $pattern->type;
            }
            $patternsquery = implode("|", $patternsquery);

            $questionsurl = quizaccess_quilgo_get_questions_url($cmid, $attemptid, $patternsquery);
            $response->questionsUrl = $questionsurl->out(false);
        }

        if (!empty($report->snapshots)) {
            $reportsnapshots = $report->snapshots;

            $snapshots = [];
            foreach ($reportsnapshots as $row) {
                $snapshot = new stdClass();
                $snapshot->id = $row->id;
                $snapshot->createdAt = $row->createdAt;
                $snapshot->url = $row->url;
                $snapshot->thumbnail = $row->thumbnail;
                $snapshot->facesAmount = $row->facesAmount;

                $snapshots[] = $snapshot;
            }

            $response->snapshots = $snapshots;
        }

        if (!empty($report->screenshots)) {
            $reportscreenshots = $report->screenshots;

            $screenshots = [];
            foreach ($reportscreenshots as $row) {
                $screenshot = new stdClass();
                $screenshot->id = $row->id;
                $screenshot->createdAt = $row->createdAt;
                $screenshot->url = $row->url;
                $screenshot->thumbnail = $row->thumbnail;
                $screenshot->isPageUnfocused = $row->isPageUnfocused;

                $screenshots[] = $screenshot;
            }

            $response->screenshots = $screenshots;
        }

        return $response;
    }

    /**
     * Define parameters when fetch stat
     * @return mixed
     */
    public static function fetch_stat_parameters() {
        return new external_function_parameters([
            'attemptid' => new external_value(PARAM_INT, 'Attempt id'),
            'cmid' => new external_value(PARAM_INT, 'Course module id'),
        ]);
    }

    /**
     * Define returns structure after fetch stat
     * @return mixed
     */
    public static function fetch_stat_returns() {
        return new external_single_structure([
            'reportStatus' => new external_value(PARAM_INT, 'Report status'),
            'confidenceLevel' => new external_value(PARAM_TEXT, 'Confidence level caption'),
            'textClass' => new external_value(PARAM_TEXT, 'Report status text class style'),
        ]);
    }

    /**
     * Handle fetch report
     * @param int $attemptid Attempt id of the report
     * @param int $cmid Course module id of the report
     * @return mixed
     */
    public static function fetch_stat($attemptid, $cmid) {
        global $DB, $PAGE;

        $params = ['attemptid' => $attemptid, 'cmid' => $cmid];
        self::validate_parameters(self::fetch_stat_parameters(), $params);

        $context = context_module::instance($cmid, MUST_EXIST);
        require_capability('quizaccess/quilgo:fetchstat', $context);

        $response = new stdClass();
        $response->reportStatus = QUIZACCESS_QUILGO_REPORT_STATUS_NOT_READY;
        $response->confidenceLevel = get_string('report_table_row_stat_not_ready', 'quizaccess_quilgo');
        $response->textClass = 'text-muted';
        $attemptsession = quizaccess_quilgo_get_attempt_session($attemptid);

        $plasmapi = new plasmapi();
        $report = $plasmapi->fetch_report($attemptsession->plasmsessionid);
        $reportresponseempty = empty($report);
        $reporterrornotfound = !$reportresponseempty && !empty($report->error) && $report->error->code === "RESOURCE_NOT_FOUND";

        if ($reportresponseempty || $reporterrornotfound) {
            $updatereport = new \stdClass();
            $updatereport->id = $attemptsession->id;
            $updatereport->generate_status = $response->reportStatus;

            $DB->update_record('quizaccess_quilgo_reports', $updatereport);

            return $response;
        }

        $confidencelevel = quizaccess_quilgo_define_confidence_level($attemptsession->camera_enabled, $report->stat);
        $response->reportStatus = QUIZACCESS_QUILGO_REPORT_STATUS_READY;
        $response->confidenceLevel = get_string('report_table_row_confidence_level_' . $confidencelevel, 'quizaccess_quilgo');
        if ($confidencelevel == QUIZACCESS_QUILGO_CONFIDENCE_LEVEL_HIGH) {
            $response->textClass = 'text-success';
        } else if ($confidencelevel == QUIZACCESS_QUILGO_CONFIDENCE_LEVEL_MODERATE) {
            $response->textClass = 'text-warning';
        } else {
            $response->textClass = 'text-danger';
        }

        if (empty($attemptsession->stat)) {
            $updatereport = new \stdClass();
            $updatereport->id = $attemptsession->id;
            $updatereport->generate_status = $response->reportStatus;
            $updatereport->stat = json_encode($report->stat);

            $DB->update_record('quizaccess_quilgo_reports', $updatereport);
        }

        return $response;
    }

    /**
     * Define parameters when fetch app data
     * @return mixed
     */
    public static function fetch_app_data_parameters() {
        return new external_function_parameters([
            'cmid' => new external_value(PARAM_INT, 'Course module id', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Define returns structure after fetch app data
     * @return mixed
     */
    public static function fetch_app_data_returns() {
        return new external_single_structure([
            'isFree' => new external_value(PARAM_BOOL, 'Application free status', VALUE_OPTIONAL),
            'name' => new external_value(PARAM_TEXT, 'Application name', VALUE_OPTIONAL),
            'planData' => new external_single_structure(
                [
                    'live' => new external_value(PARAM_BOOL, 'Plan live status'),
                    'concurrencyLimit' => new external_value(PARAM_INT, 'Plan concurrency soft limit'),
                    'concurrencyHardLimit' => new external_value(PARAM_INT, 'Plan concurrency hard limit'),
                    'priceUSD' => new external_value(PARAM_INT, 'Plan price monthly'),
                    'priceUSD12' => new external_value(PARAM_INT, 'Plan price yearly'),
                    'title' => new external_value(PARAM_TEXT, 'Plan title'),
                ],
                'Plan data',
                VALUE_OPTIONAL,
                null,
            ),
            'currency' => new external_value(PARAM_TEXT, 'Application active currency', VALUE_OPTIONAL),
            'planProduct' => new external_value(PARAM_TEXT, 'Application plan product', VALUE_OPTIONAL),
            'planTitle' => new external_value(PARAM_TEXT, 'Application plan title', VALUE_OPTIONAL),
            'currentConcurrentSessionsMax' => new external_value(PARAM_INT, 'Current concurrent sessions max', VALUE_OPTIONAL),
            'concurrentSessionsLimit' => new external_value(PARAM_INT, 'Concurrent sessions soft limit', VALUE_OPTIONAL),
            'concurrentSessionsHardLimit' => new external_value(PARAM_INT, 'Concurrent sessions hard limit', VALUE_OPTIONAL),
            'isSoftUnsubscribed' => new external_value(PARAM_BOOL, 'Soft unsubscribe status', VALUE_OPTIONAL),
            'currentConcurrentSessions' => new external_value(PARAM_INT, 'Current concurrent sessions', VALUE_OPTIONAL),
            'email' => new external_value(PARAM_TEXT, 'Application email', VALUE_OPTIONAL),
            'confirmedEmail' => new external_value(PARAM_TEXT, 'Application metadata confirmed email', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Handle fetch app data
     * @param int|null $cmid course module id
     * @return mixed
     */
    public static function fetch_app_data($cmid = null) {
        // NOTE: we set $cmid as optional because this service used as well on API config page.
        $params = ['cmid' => $cmid];
        self::validate_parameters(self::fetch_app_data_parameters(), $params);

        if (empty($cmid)) {
            $contextsystem = context_system::instance();
            require_capability('moodle/site:config', $contextsystem);
        } else {
            $contextmodule = context_module::instance($cmid, MUST_EXIST);
            require_capability('quizaccess/quilgo:fetchappdata', $contextmodule);
        }

        $plasmapi = new plasmapi();
        $appdata = $plasmapi->fetch_application_data();

        $response = new stdClass();
        if (!empty($appdata)) {
            $response = $appdata;
            $response->isFree = $appdata->planProduct === "FREE";
        }

        return $response;
    }

    /**
     * Define parameters when generate app dashboard url
     * @return mixed
     */
    public static function generate_app_dashboard_url_parameters() {
        return new external_function_parameters([]);
    }

    /**
     * Define returns structure after generate app dashboard url
     * @return mixed
     */
    public static function generate_app_dashboard_url_returns() {
        return new external_single_structure([
            'dashboardUrl' => new external_value(PARAM_RAW, 'Application dashboard url', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Handle generate app dashboard url
     * @return mixed
     */
    public static function generate_app_dashboard_url() {
        self::validate_parameters(self::generate_app_dashboard_url_parameters(), []);

        $context = context_system::instance();
        require_capability('moodle/site:config', $context);

        $plasmapi = new plasmapi();
        $dashboardtoken = $plasmapi->generate_dashboard_token();

        $response = new stdClass();
        if (!empty($dashboardtoken)) {
            $response->dashboardUrl = quizaccess_quilgo_get_application_dashboard_url($dashboardtoken->token);
        }

        return $response;
    }

    /**
     * Define parameters when confirm email
     * @return mixed
     */
    public static function confirm_email_parameters() {
        return new external_function_parameters([
            'isrequesttoasklater' => new external_value(PARAM_BOOL, 'Is request to ask later or not', VALUE_REQUIRED),
            'email' => new external_value(PARAM_EMAIL, "Confirmed email", VALUE_OPTIONAL),
        ]);
    }

    /**
     * Define returns structure after generate app dashboard url
     * @return mixed
     */
    public static function confirm_email_returns() {
        return new external_single_structure([
            'issuccess' => new external_value(PARAM_BOOL, 'Is process success or not', VALUE_REQUIRED),
        ]);
    }

    /**
     * Handle confirm email
     * @param bool $isrequesttoasklater request to ask again later
     * @param string $email confirmed email
     * @return mixed
     */
    public static function confirm_email($isrequesttoasklater, $email = "") {
        $params = ['isrequesttoasklater' => $isrequesttoasklater, 'email' => $email];
        self::validate_parameters(self::confirm_email_parameters(), $params);

        $context = context_system::instance();
        require_capability('moodle/site:config', $context);

        $issuccess = true;
        if ($isrequesttoasklater) {
            set_config(QUIZACCESS_QUILGO_CONFIG_REQUEST_ASK_LATER_FOR_EMAIL_CONFIMRATION_AT, time(), "quizaccess_quilgo");
        } else {
            $plasmapi = new plasmapi();
            $issuccess = $plasmapi->set_application_metadata($email);

            if ($issuccess) {
                set_config(QUIZACCESS_QUILGO_CONFIG_CONFIRMED_EMAIL, $email, "quizaccess_quilgo");
            }
        }

        $response = new stdClass();
        $response->issuccess = $issuccess;

        return $response;
    }

    /**
     * Define parameters when fetch csv base structure
     * @return mixed
     */
    public static function fetch_csv_quiz_structure_parameters() {
        return new external_function_parameters([
            'cmid' => new external_value(PARAM_INT, 'Course module id', VALUE_REQUIRED),
            'quizid' => new external_value(PARAM_INT, 'Quiz id', VALUE_REQUIRED),
        ]);
    }

    /**
     * Define returns structure after fetch csv quiz structure
     * @return mixed
     */
    public static function fetch_csv_quiz_structure_returns() {
        return new external_single_structure([
            'quizgrades' => new external_single_structure(
                [
                    'sumgrades' => new external_value(PARAM_TEXT, 'Quiz sumgrades'),
                    'grade' => new external_value(PARAM_TEXT, 'Quiz grade'),
                    'decimalpoints' => new external_value(PARAM_TEXT, 'Quiz decimalpoints'),
                ],
                'Quiz grades data',
                VALUE_REQUIRED
            ),
            'slots' => new external_multiple_structure(
                new external_single_structure([
                    'slot' => new external_value(PARAM_TEXT, 'Structure slot'),
                    'number' => new external_value(PARAM_TEXT, 'Structure slot number'),
                    'maxmark' => new external_value(PARAM_TEXT, 'Structure maxmark'),
                ]),
                'Quiz slots structure',
                VALUE_REQUIRED,
            ),
        ]);
    }

    /**
     * Handle fetch csv quiz structure
     * @param int $cmid course module id
     * @param int $quizid quiz id
     * @return mixed
     */
    public static function fetch_csv_quiz_structure($cmid, $quizid) {
        $params = ['cmid' => $cmid, 'quizid' => $quizid];
        self::validate_parameters(self::fetch_csv_quiz_structure_parameters(), $params);

        $contextmodule = context_module::instance($cmid, MUST_EXIST);
        require_capability('quizaccess/quilgo:fetchcsvquizstructure', $contextmodule);

        $reportcsv = new report_csv();
        $quizslots = $reportcsv->get_quiz_slots($quizid);
        $quizsettings = $reportcsv->get_quiz_settings($quizid);
        $quizdata = $quizsettings->get_quiz();
        $quizsumgrades = $quizdata->sumgrades;
        $quizgrade = $quizdata->grade;
        $quizdecimalpoints = $quizdata->decimalpoints;

        $response = new stdClass();

        $quizgrades = new stdClass();
        $quizgrades->sumgrades = $quizsumgrades;
        $quizgrades->grade = $quizgrade;
        $quizgrades->decimalpoints = $quizdecimalpoints;
        $response->quizgrades = $quizgrades;

        $responseslots = [];
        foreach ($quizslots as $quizslot) {
            $slot = new stdClass();
            $slot->slot = $quizslot->slot;
            $slot->number = $quizslot->number;
            $slot->maxmark = $quizslot->maxmark;
            $responseslots[] = $slot;
        }
        $response->slots = $responseslots;

        return $response;
    }

    /**
     * Define parameters when fetch csv records
     * @return mixed
     */
    public static function fetch_csv_records_parameters() {
        return new external_function_parameters([
            'cmid' => new external_value(PARAM_INT, 'Course module id', VALUE_REQUIRED),
            'quizid' => new external_value(PARAM_INT, 'Quiz id', VALUE_REQUIRED),
            'quizstructure' => new external_single_structure([
                'quizgrades' => new external_single_structure(
                    [
                        'sumgrades' => new external_value(PARAM_TEXT, 'Quiz sumgrades'),
                        'grade' => new external_value(PARAM_TEXT, 'Quiz grade'),
                        'decimalpoints' => new external_value(PARAM_TEXT, 'Quiz decimalpoints'),
                    ],
                    'Quiz grades data',
                    VALUE_REQUIRED
                ),
                'slots' => new external_multiple_structure(
                    new external_single_structure([
                        'slot' => new external_value(PARAM_TEXT, 'Structure slot'),
                        'number' => new external_value(PARAM_TEXT, 'Structure slot number'),
                        'maxmark' => new external_value(PARAM_TEXT, 'Structure maxmark'),
                    ]),
                    'Quiz slots structure',
                    VALUE_REQUIRED,
                ),
            ]),
            'lastattemptid' => new external_value(PARAM_TEXT, 'Attempt last id', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Define returns when fetch csv records
     * @return mixed
     */
    public static function fetch_csv_records_returns() {
        return new external_single_structure([
            'headers' => new external_value(PARAM_RAW, 'Csv headers', VALUE_REQUIRED),
            'rowsamount' => new external_value(PARAM_NUMBER, 'Csv rows amount', VALUE_OPTIONAL),
            'rows' => new external_value(PARAM_RAW, 'Csv rows', VALUE_REQUIRED),
            'lastattemptid' => new external_value(PARAM_TEXT, 'Csv last attempt id', VALUE_OPTIONAL),
        ]);
    }

    /**
     * Handle export csv
     * @param int $cmid course module id
     * @param int $quizid quiz id
     * @param mixed $quizstructure quiz grades and slots
     * @param int $lastattemptid last attempt id for paginate the records
     * @return mixed
     */
    public static function fetch_csv_records($cmid, $quizid, $quizstructure, $lastattemptid) {
        $params = [
            'cmid' => $cmid,
            'quizid' => $quizid,
            'quizstructure' => $quizstructure,
            'lastattemptid' => $lastattemptid,
        ];
        self::validate_parameters(self::fetch_csv_records_parameters(), $params);

        $contextmodule = context_module::instance($cmid, MUST_EXIST);
        require_capability('quizaccess/quilgo:fetchcsvquizstructure', $contextmodule);

        $reportcsv = new report_csv();
        $response = $reportcsv->get_records_for_csv($quizid, $quizstructure, $lastattemptid);

        return $response;
    }
}
