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

/**
 * Library of functions for uploading a course enrolment methods CSV file.
 *
 * @package    local_treestudyplan
 * @copyright  2025 P.M. Kuipers
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
namespace local_treestudyplan\local\tools;

use DateTime;
use Exception;

defined('MOODLE_INTERNAL') || die;

require_once($CFG->dirroot . '/lib/enrollib.php');
require_once($CFG->dirroot . '/enrol/meta/locallib.php');
require_once($CFG->dirroot . '/enrol/cohort/lib.php');
require_once($CFG->dirroot . '/enrol/cohort/locallib.php');
require_once($CFG->dirroot . '/admin/tool/uploaduser/locallib.php');


/**
 * Validates and processes files for uploading managed shares CSV file
 */
class uploadshares_processor {
    /** @var string[] */
    const REQUIRED_COLUMNS = ["username", "inviteename", "inviteemail", "unlockdate"];
    /** @var \csv_import_reader */
    protected $cir;

    /** @var array CSV columns. */
    protected $columns = [];

    /** @var int line number. */
    protected $linenb = 0;

    /**
     * Constructor, sets the CSV file reader
     *
     * @param csv_import_reader $cir import reader object
     */
    public function __construct(\csv_import_reader $cir) {
        $this->cir = $cir;
        $this->columns = $cir->get_columns();
        $this->validate();
        $this->reset();
        $this->linenb++;
    }

    /**
     * Processes the file to handle the enrolment methods
     *
     * Opens the file, loops through each row. Cleans the values in each column,
     * checks that the operation is valid and the methods exist. If all is well,
     * adds, updates or deletes the enrolment method metalink in column 3 to/from the course in column 2
     * context as specified.
     * Returns a report of successes and failures.
     *
     * @see open_file()
     * @uses enrol_meta_sync() Meta plugin function for syncing users
     * @return string A report of successes and failures.
     *
     * @param object $tracker the output tracker to use.
     * @return void
     */
    public function execute($tracker = null) {
        global $DB;

        if (empty($tracker)) {
            $tracker = new uploadshares_tracker(uploadshares_tracker::NO_OUTPUT);
        }

        // Initialise the output heading row labels.
        $reportheadings = [
            'line' => get_string('csvline', 'tool_uploadcourse'),
            'user' => get_string('user'),
            'inviteename' => get_string('invite_name', 'local_treestudyplan'),
            'inviteemail' => get_string('invite_email', 'local_treestudyplan'),
            'unlockdate' => get_string('invite_unlock_date', 'local_treestudyplan'),
            'identifier' => get_string('invite_identifier', 'local_treestudyplan'),
            'result' => get_string('result', 'local_treestudyplan'),
        ];
        $tracker->start($reportheadings, true);

        // Initialise some counters to summarise the results.
        $total = 0;
        $created = 0;
        $skipped = 0;
        $updated = 0;
        $errors = 0;

        // We will most certainly need extra time and memory to process big files.
        \core_php_time_limit::raise();
        raise_memory_limit(MEMORY_EXTRA);

        // Prepare reporting message strings.
        $messagerow = [];

        // Define some class variables.

        $today = new \DateTime("now", \core_date::get_user_timezone_object());
        $today->setTime(0, 0, 0);

        // Loop through each row of the file.
        while ($line = $this->cir->next()) {
            $this->linenb++;
            $total++;

            // Read in and process one data line from the CSV file.
            $data = $this->parse_line($line);
            $username = trim($data['username']);
            $inviteename = trim($data['inviteename']);
            $inviteemail = trim($data['inviteemail']);
            $unlockdate = trim($data['unlockdate']);
            $identifier = trim($data['identifier']);

            // Add line-specific reporting message strings.
            $messagerow['line'] = $this->linenb;
            $messagerow['user'] = $username;
            $messagerow['inviteename'] = $inviteename;
            $messagerow['inviteemail'] = $inviteemail;
            $messagerow['unlockdate'] = $unlockdate;
            $messagerow['identifier'] = $identifier;
            // Need to check the line is valid. If not, add a message to the report and skip the line.

            // Check if user exists.
            $user = $DB->get_record("user", ['username' => $username]);
            if (empty($user)) {
                $errors++;
                $messagerow['result'] = get_string("uploadshares_result:usernotfound", 'local_treestudyplan');
                $tracker->output($messagerow, false);
                continue;
            }

            $messagerow['user'] = \core_user::get_fullname($user);

            // Check if email address is valid.
            if (!filter_var($inviteemail, FILTER_VALIDATE_EMAIL)) {
                $errors++;
                $messagerow['result'] = get_string("uploadshares_result:emailinvalid", 'local_treestudyplan');
                $tracker->output($messagerow, false);
                continue;
            }

            // Check if date string is valid.
            try {
                $d = new DateTime($unlockdate);
                if ($d < $today) {
                    $errors++;
                    $messagerow['result'] = get_string("uploadshares_result:datepast", 'local_treestudyplan');
                    $tracker->output($messagerow, false);
                    continue;
                }
                $unlocktimestamp = $d->getTimestamp();
            } catch (\Exception $x) {
                $errors++;
                $messagerow['result'] = get_string("uploadshares_result:dateinvalid", 'local_treestudyplan');
                $tracker->output($messagerow, false);
                continue;
            }

            // Find existing records.
            $sql = "SELECT i.*
                    FROM {local_treestudyplan_invit} i
                    WHERE i.user_id = :userid
                        AND i.unlockdate IS NOT NULL AND i.unlockdate > 0
                        AND (i.email = :inviteemail
                            OR (i.identifier = :identifier AND i.identifier IS NOT NULL AND LENGTH(TRIM(i.identifier)) > 0 )
                        )";
            $params = [
                'userid' => $user->id,
                'inviteemail' => $inviteemail,
                'identifier' => $identifier,
            ];
            $r = $DB->get_record_sql($sql, $params);
            if (!empty($r)) {
                // If record exists, check if an update is needed.
                if (
                    $r->unlockdate == $unlocktimestamp
                    && $r->name == $inviteename
                    && $r->email == $inviteemail
                    && $r->identifier == $identifier
                ) {
                    // No changes - skip.
                    $skipped++;
                    $messagerow['result'] = get_string("uploadshares_result:exists", 'local_treestudyplan');
                    $tracker->output($messagerow, true, true);
                    continue;
                } else {
                    $resend = ($r->email != $inviteemail) ? true : false;
                    // Need to update the record.
                    $r->unlockdate = $unlocktimestamp;
                    $r->name = $inviteename;
                    $r->email = $inviteemail;
                    $r->identifier = $identifier;
                    $DB->update_record('local_treestudyplan_invit', $r);

                    $updated++;
                    $messagerow['result'] = get_string("uploadshares_result:updated", 'local_treestudyplan');
                    $tracker->output($messagerow, true);

                    if ($resend) {
                        invitation_tools::send_invite($r->id);
                    }

                    continue;
                }
            } else {
                // Create new record.
                $r = new \stdClass();
                $r->user_id = $user->id;
                $r->name = $inviteename;
                $r->email = $inviteemail;
                $r->idate = $today->getTimestamp();
                $r->unlockdate = $unlocktimestamp;
                $r->identifier = $identifier;
                $r->invitekey = invitation_tools::generate_invitekey();

                $id = $DB->insert_record('local_treestudyplan_invit', $r);

                $created++;
                $messagerow['result'] = get_string("uploadshares_result:created", 'local_treestudyplan');
                $tracker->output($messagerow, true);

                invitation_tools::send_invite($id);

                continue;
            }
            // Checks are mostly now done, so let's get to work.
        } // End of while loop.

        $message = [
            get_string('uploadshares_total', 'local_treestudyplan', $total),
            get_string('uploadshares_created', 'local_treestudyplan', $created),
            get_string('uploadshares_updated', 'local_treestudyplan', $updated),
            get_string('uploadshares_skipped', 'local_treestudyplan', $skipped),
            get_string('uploadshares_errors', 'local_treestudyplan', $errors),
        ];

        $tracker->finish();
        $tracker->results($message);
    }

    /**
     * Parse a line to return an array(column => value)
     *
     * @param array $line returned by csv_import_reader
     * @return array
     */
    protected function parse_line($line) {
        $data = [];
        foreach ($line as $keynum => $value) {
            if (!isset($this->columns[$keynum])) {
                // This should not happen.
                continue;
            }

            $key = $this->columns[$keynum];
            $data[$key] = $value;
        }
        return $data;
    }

    /**
     * Reset the current process.
     *
     * @return void.
     */
    public function reset() {
        $this->processstarted = false;
        $this->linenb = 0;
        $this->cir->init();
        $this->errors = [];
    }

    /**
     * Validation.
     *
     * @return void
     */
    protected function validate() {
        if (empty($this->columns)) {
            throw new \moodle_exception('cannotreadtmpfile', 'error');
        } else {
            // Check if the mandatory columns are present.
            $missing = [];
            foreach (self::REQUIRED_COLUMNS as $col) {
                if (!in_array($col, $this->columns)) {
                    $missing[] = $col;
                }
            }

            // Throw an explanatory error if any are missing.
            if (count($missing) > 0) {
                throw new \moodle_exception('csvcolumnmissing', 'local_treestudyplan', '', implode(", ", $missing));
            }
        }
    }
}
