<?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 quizaccess_quilgo\local\report;

use stdClass;
use quizaccess_quilgo\plasmapi;
use quizaccess_quilgo\local\utils;

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

// Starting moodle 4.2, 'quiz_attempt' from autoloader is depcrecated. Suggested to use from 'mod\quiz_attempt'.
require_once($CFG->libdir . '/tablelib.php');
require_once($CFG->dirroot . '/mod/quiz/accessrule/quilgo/lib.php');
require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
if (class_exists('mod_quiz\quiz_attempt')) {
    class_alias('mod_quiz\quiz_attempt', '\quiz_attempt_alias');
} else {
    require_once($CFG->dirroot . '/mod/quiz/locallib.php');
    class_alias('\quiz_attempt', '\quiz_attempt_alias');
}

// Starting moodle 4.2, 'quiz' from autoloader is depcrecated. Suggested to use from 'mod\quiz_settings'.
if (class_exists('mod_quiz\quiz_settings')) {
    class_alias('\mod_quiz\quiz_settings', '\quiz_settings_alias');
} else {
    require_once($CFG->dirroot . '/mod/quiz/locallib.php');
    class_alias('\quiz', '\quiz_settings_alias');
}

/**
 * Class for handle proctoring report CSV export
 * @package     quizaccess_quilgo
 * @copyright   2025 Native Platform Ltd <hello@quilgo.com>
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class report_csv {
    /**
     * @var int export csv record per page
     */
    protected $exportperpage = 20;

    /**
     * Get quiz slots
     * @param int $quizid quiz id
     * @return array
     */
    public function get_quiz_slots($quizid) {
        global $DB;

        $quiz = new stdClass();
        $quiz->id = $quizid;
        $questions = quiz_report_get_significant_questions($quiz);

        return $questions;
    }

    /**
     * Get quiz settings
     * @param int $quizid quiz id
     * @return mixed
     */
    public function get_quiz_settings($quizid) {
        return \quiz_settings_alias::create($quizid);
    }

    /**
     * Get attempt records base sql properties
     * @param int $quizid quiz id
     * @param int|null $lastattemptid last attempt id
     * @return array
     */
    protected function get_attempts_sql_properties($quizid, $lastattemptid = null) {
        global $DB;

        $attemptfinishedstate = \quiz_attempt_alias::FINISHED;
        $nameconcatsql = $DB->sql_concat_join("' '", ['u.firstname', 'u.lastname']);

        $fields = " a.id, u.id AS user_id, u.email, $nameconcatsql AS userfullname,
                    a.attempt, r.plasmsessionid, r.camera_enabled, a.sumgrades,
                    a.timestart, a.timefinish, r.screen_enabled, r.force_enabled,
                    r.stat, a.uniqueid
        ";

        $from = " {quiz_attempts} a
                    JOIN {user} u ON u.id = a.userid
                    JOIN {quizaccess_quilgo_reports} r ON r.attemptid = a.id
        ";

        $where = "r.plasmsessionid IS NOT NULL AND a.quiz = :quizid
                    AND a.state = :attemptfinishedstate
        ";
        $params = [
            "quizid" => $quizid,
            "attemptfinishedstate" => $attemptfinishedstate,
        ];

        if ($lastattemptid != null) {
            $where .= " AND a.id < :lastattemptid";
            $params["lastattemptid"] = $lastattemptid;
        }

        return [$fields, $from, $where, $params];
    }

    /**
     * Get attempt records with apply limit
     * @param int $quizid quiz id
     * @param int|null $lastattemptid last attempt id
     * @return array
     */
    protected function get_attempt_records($quizid, $lastattemptid = null) {
        global $DB;

        list($fields, $from, $where, $params) = $this->get_attempts_sql_properties($quizid, $lastattemptid);

        $sql = "SELECT $fields FROM $from WHERE $where ORDER BY a.id DESC LIMIT $this->exportperpage";
        $rows = $DB->get_records_sql($sql, $params);

        $attempts = [];
        foreach ($rows as $row) {
            $attempts[] = $row;
        }

        return $attempts;
    }

    /**
     * Get attempt records count
     * @param int $quizid quiz id
     * @return number
     */
    public function get_attempt_records_count($quizid) {
        global $DB;

        list(, $from, $where, $params) = $this->get_attempts_sql_properties($quizid);

        $sql = "SELECT COUNT(1) FROM $from WHERE $where";
        $rowscount = $DB->count_records_sql($sql, $params);

        return $rowscount;
    }

    /**
     * Get proctoring reports and build with array session id index
     * @param array $sessionids session ids want to get the report
     * @return array
     */
    public function get_and_build_proctoring_reports($sessionids) {
        $proctoringreports = [];
        if (count($sessionids) > 0) {
            $plasmapi = new plasmapi();
            $reportsresponse = $plasmapi->fetch_multiple_reports(implode(",", $sessionids));
            foreach ($reportsresponse as $reportresponse) {
                $proctoringreports[$reportresponse->uuid] = $reportresponse;
            }
        }

        return $proctoringreports;
    }

    /**
     * Get question grades and build array with key attemptid-questionslot
     * @param array $attemptids Attemptids that want to get the question grades
     * @return array
     */
    public function get_and_build_question_grades($attemptids) {
        global $DB;

        $questiongrades = [];

        if (count($attemptids) > 0) {
            $attemptidsparamtext = [];
            $attemptidsparam = [];
            foreach ($attemptids as $attemptid) {
                $attemptidkey = "attemptid$attemptid";
                $attemptidsparamtext[] = ":$attemptidkey";
                $attemptidsparam[$attemptidkey] = $attemptid;
            }

            $attemptidsparamtext = implode(',', $attemptidsparamtext);
            $rowkey = $DB->sql_concat_join("'-'", ['qa.uniqueid', 'queatt.slot']);

            $sql = "SELECT $rowkey AS questionslotgradeid, queattst.fraction,
                queatt.responsesummary
                FROM {question_attempts} queatt
                JOIN {question_attempt_steps} queattst
                    ON queattst.questionattemptid = queatt.id
                    AND queattst.sequencenumber = (
                        SELECT MAX(sequencenumber)
                        FROM {question_attempt_steps}
                        WHERE questionattemptid = queatt.id
                    )
                JOIN {quiz_attempts} qa ON qa.uniqueid = queatt.questionusageid
                WHERE qa.id IN ($attemptidsparamtext)
            ";

            $questiongrades = $DB->get_records_sql($sql, $attemptidsparam);
        }

        return $questiongrades;
    }

    /**
     * Get records before exported to csv
     * @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 function get_records_for_csv($quizid, $quizstructure, $lastattemptid = null) {
        $attempts = $this->get_attempt_records($quizid, $lastattemptid);
        $quizsettings = $this->get_quiz_settings($quizid);
        $quizdata = $quizsettings->get_quiz();
        $quizdecimalpoints = $quizdata->decimalpoints;

        $csv = new stdClass();
        $csv->rowsamount = $lastattemptid == null
            ? $this->get_attempt_records_count($quizid)
            : null;

        $attemptsamount = count($attempts);
        $csv->lastattemptid = $attemptsamount > 0 ? $attempts[$attemptsamount - 1]->id : null;

        $structuregrades = $quizstructure["quizgrades"];
        $structureslots = $quizstructure["slots"];

        $sessionids = [];
        $attemptids = [];
        foreach ($attempts as $attempt) {
            $attemptids[] = $attempt->id;
            if ($attempt->stat != null) {
                $sessionids[] = $attempt->plasmsessionid;
            }
        }

        $proctoringreports = $this->get_and_build_proctoring_reports($sessionids);
        $questiongrades = $this->get_and_build_question_grades($attemptids);

        $headers = [
            get_string('report_table_header_email', 'quizaccess_quilgo'),
            get_string('report_table_header_name', 'quizaccess_quilgo'),
            get_string('report_table_header_attempt', 'quizaccess_quilgo'),
            get_string('report_table_header_settings', 'quizaccess_quilgo'),
            get_string('report_table_header_activity_tracking_enabled', 'quizaccess_quilgo'),
            get_string('report_table_header_camera_tracking_enabled', 'quizaccess_quilgo'),
            get_string('report_table_header_screen_tracking_enabled', 'quizaccess_quilgo'),
            get_string('report_table_header_force_tracking_enabled', 'quizaccess_quilgo'),
            get_string('report_table_header_time_information', 'quizaccess_quilgo'),
            get_string('report_table_header_timestart', 'quizaccess_quilgo'),
            get_string('report_table_header_timefinish', 'quizaccess_quilgo'),
            get_string('report_table_header_results', 'quizaccess_quilgo'),
            get_string('report_table_header_time_taken', 'quizaccess_quilgo'),
            get_string('report_table_header_confidence_levels', 'quizaccess_quilgo'),
            get_string('report_table_header_activity', 'quizaccess_quilgo'),
            get_string('report_table_header_face_presence', 'quizaccess_quilgo'),
            get_string('report_table_header_screenshots', 'quizaccess_quilgo'),
            get_string(
                'report_table_header_grade',
                'quizaccess_quilgo',
                format_float($structuregrades["grade"], $quizdecimalpoints),
            ),
        ];

        foreach ($structureslots as $structureslot) {
            $slotgrade = ($structureslot["maxmark"] / $structuregrades["sumgrades"]) * $structuregrades["grade"];
            $headers[] = get_string(
                'report_table_header_question',
                'quizaccess_quilgo',
                $structureslot["number"]
            ) . " /" . format_float($slotgrade, $quizdecimalpoints);

            $headers[] = get_string('report_table_header_suspicious_patterns', 'quizaccess_quilgo');
        }

        $csv->headers = json_encode($headers);

        $yesstring = get_string('general_yes', 'quizaccess_quilgo');
        $nostring = get_string('general_no', 'quizaccess_quilgo');
        $datetimestring = get_string('strftimedatetime', 'langconfig');
        $rows = [];
        foreach ($attempts as $attempt) {
            $row = [];
            $row[] = $attempt->email;
            $row[] = $attempt->userfullname;
            $row[] = $attempt->attempt;

            // Settings.
            $row[] = " ";
            // Navigation tracking always enabled.
            $row[] = $yesstring;
            $row[] = $attempt->camera_enabled === "1" ? $yesstring : $nostring;
            $row[] = $attempt->screen_enabled === "1" ? $yesstring : $nostring;
            $row[] = $attempt->force_enabled === "1" ? $yesstring : $nostring;

            // Time information.
            $row[] = " ";
            $row[] = userdate($attempt->timestart, $datetimestring);
            $row[] = userdate($attempt->timefinish, $datetimestring);

            // Results.
            $row[] = " ";
            // Time taken.
            $timetakencontents = " ";
            $timetaken = $attempt->timefinish - $attempt->timestart;
            if ($timetaken) {
                $timetakencontents = format_time($timetaken);
                if ($quizdata->timelimit && $timetaken > ($quizdata->timelimit + 60)) {
                    $overtime = $timetaken - $quizdata->timelimit;
                    $overtime = format_time($overtime);
                    $timetakencontents .= " " . get_string('report_table_row_overdue', 'quizaccess_quilgo', $overtime);
                }
            }
            $row[] = $timetakencontents;

            $proctoringreport = $proctoringreports[$attempt->plasmsessionid];
            $proctoringstat = null;
            $confidenceleveltext = "";
            $activitytext = "";
            $facepresencetext = "";
            $suspiciousscreenshotstext = "";

            if ($proctoringreport != null) {
                $proctoringstat = json_decode($proctoringreport->stat);

                // Confidence level.
                $confidencelevel = quizaccess_quilgo_define_confidence_level(
                    (int) $attempt->camera_enabled,
                    $proctoringstat
                );
                $confidenceleveltext = get_string(
                    'report_table_row_confidence_level_' . $confidencelevel, 'quizaccess_quilgo'
                );

                // Activity.
                $pageunfocusedcount = $proctoringstat->pageUnfocusedCount;
                $activitytext = get_string('report_focus_good', 'quizaccess_quilgo');
                if ($pageunfocusedcount > 0) {
                    $activitytext = $pageunfocusedcount > 1
                        ? get_string('report_focus_not_good_multiple', 'quizaccess_quilgo', $pageunfocusedcount)
                        : get_string('report_focus_not_good', 'quizaccess_quilgo', $pageunfocusedcount);
                }

                // Face presence.
                $facepresencetext = "";
                if ($proctoringstat->facesPresence != null) {
                    $facepresencepct = $proctoringstat->facesPresence * 100;
                    $facepresencetext = $facepresencepct . "%";
                }

                // Suspicious screenshots.
                $suspiciousscreenshotstext = "";
                if ($proctoringreport->screenshots != null) {
                    $suspiciouscreenshotscount = 0;
                    foreach ($proctoringreport->screenshots as $screenshot) {
                        if ($screenshot->isPageUnfocused) {
                            $suspiciouscreenshotscount += 1;
                        }
                    }

                    if ($suspiciouscreenshotscount > 0) {
                        $suspiciousscreenshotstext = get_string(
                            'report_suspicious_caption',
                            'quizaccess_quilgo',
                            $suspiciouscreenshotscount,
                        );
                    }
                }
            }

            $row[] = $confidenceleveltext;
            $row[] = $activitytext;
            $row[] = $facepresencetext;
            $row[] = $suspiciousscreenshotstext;

            $gradetext = get_string('report_table_row_notyetgraded', 'quizaccess_quilgo');
            if ($attempt->sumgrades != null) {
                $gradecalculation = ($attempt->sumgrades / $structuregrades["sumgrades"]) * $structuregrades["grade"];
                $gradetext = format_float($gradecalculation, $quizdecimalpoints);
            }
            $row[] = $gradetext;

            foreach ($structureslots as $structureslot) {
                $questiongradekey = $attempt->uniqueid . "-" . $structureslot["slot"];

                $slotgradetext = "";
                if (isset($questiongrades[$questiongradekey])) {
                    $questiongrade = $questiongrades[$questiongradekey];
                    $slotgradetext = get_string('report_table_row_requires_grading', 'quizaccess_quilgo');
                    if ($questiongrade->responsesummary == null) {
                        $slotgradetext = "-";
                    } else if ($questiongrade->fraction != null) {
                        $slotgrade = ($structureslot["maxmark"] / $structuregrades["sumgrades"]) * $structuregrades["grade"];
                        $questiongradecalculation = $slotgrade * $questiongrade->fraction;
                        $slotgradetext = format_float($questiongradecalculation, $quizdecimalpoints);
                    }
                }
                $row[] = $slotgradetext;

                $patternstext = "";
                if ($proctoringstat != null) {
                    $patternquestionkey = "question-" . $attempt->uniqueid . "-" . $structureslot["slot"];
                    $patternperquestionindex = array_search(
                        $patternquestionkey,
                        array_column($proctoringstat->patterns, 'questionId')
                    );

                    if ($patternperquestionindex !== false) {
                        $questionpattern = $proctoringstat->patterns[$patternperquestionindex];
                        $patternsteps = utils::build_pattern_array_steps($questionpattern->type);
                        $patternstext = implode(' > ', $patternsteps);
                    }
                }
                $row[] = $patternstext;
            }

            // Add to rows.
            $rows[] = $row;
        }
        $csv->rows = json_encode($rows);

        return $csv;
    }
}
