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

/**
 * Export ADESI (section 21 du cahier des charges) : deux fichiers CSV
 * (niveaux, commentaires globaux), construits **uniquement** à partir des
 * dossiers CAPP finalisés de la session sélectionnée — jamais depuis les
 * données intermédiaires d'enseignements/stages (celles-ci n'alimentent que
 * la vue consultative du CAPP, jamais l'export).
 *
 * `preview()` et les deux `build_*_csv()` exécutent exactement la même
 * validation — jamais une confiance aveugle dans un état "prévisualisé"
 * côté client, le téléchargement revalide toujours intégralement côté
 * serveur (section 21.8/21.9).
 *
 * @package local_kid
 */

namespace local_kid\local;

use local_kid\event\adesi_exported;

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

class adesi_export_service {

    /**
     * @return array{promotionname:string, sessioncode:string,
     *   niveaux_rowcount:int, commentaires_rowcount:int, errors:string[],
     *   blocked:bool, norows:bool}
     */
    public static function preview(int $promotionid, int $sessionid): array {
        [$validation] = self::validate($promotionid, $sessionid);
        return $validation;
    }

    /**
     * @return array{success:bool, content:?string, filename:?string,
     *   rowcount:int, errors:string[]}
     */
    public static function export_niveaux(int $promotionid, int $sessionid, int $userid): array {
        [$validation, $context] = self::validate($promotionid, $sessionid);
        if ($validation['blocked']) {
            self::log($promotionid, $sessionid, 'niveaux', $userid, 0, count($validation['errors']), $validation['errors'], null);
            return ['success' => false, 'content' => null, 'filename' => null, 'rowcount' => 0, 'errors' => $validation['errors']];
        }

        $lines = ['code_ena;id_competence;niveau_competence;'];
        foreach ($context['finalised'] as $entry) {
            $codeena = codeena_resolver::resolve_for_adesi_export((int) $entry->userid, $entry->email, $entry->firstname, $entry->lastname);
            $results = capp_repository::get_results((int) $entry->id);
            foreach ($context['competencies'] as $competency) {
                $result = $results[$competency->id] ?? null;
                $niveauid = $result->niveauid ?? null;
                $codeadesiniveau = $niveauid && isset($context['niveauxbyid'][$niveauid])
                    ? $context['niveauxbyid'][$niveauid]->codeadesi
                    : $context['defaultcodeadesi'];
                $lines[] = implode(';', [$codeena, $competency->codeadesi, $codeadesiniveau]) . ';';
            }
        }

        $content = implode("\r\n", $lines) . "\r\n";
        $rowcount = count($lines) - 1;
        $filename = self::filename('adesi_niveaux', $context['promotion']->name, $context['session']->code);
        self::log($promotionid, $sessionid, 'niveaux', $userid, $rowcount, 0, [], $filename);

        return ['success' => true, 'content' => $content, 'filename' => $filename, 'rowcount' => $rowcount, 'errors' => []];
    }

    /**
     * @return array{success:bool, content:?string, filename:?string,
     *   rowcount:int, errors:string[]}
     */
    public static function export_commentaires(int $promotionid, int $sessionid, int $userid): array {
        [$validation, $context] = self::validate($promotionid, $sessionid);
        if ($validation['blocked']) {
            self::log($promotionid, $sessionid, 'commentaires', $userid, 0, count($validation['errors']), $validation['errors'], null);
            return ['success' => false, 'content' => null, 'filename' => null, 'rowcount' => 0, 'errors' => $validation['errors']];
        }

        $lines = ['code_ena;commentaire;'];
        foreach ($context['finalised'] as $entry) {
            $codeena = codeena_resolver::resolve_for_adesi_export((int) $entry->userid, $entry->email, $entry->firstname, $entry->lastname);
            $commentaire = str_replace([';', "\r", "\n"], [',', ' ', ' '], (string) ($entry->appreciationglobale ?? ''));
            $lines[] = implode(';', [$codeena, $commentaire]) . ';';
        }

        $content = implode("\r\n", $lines) . "\r\n";
        $rowcount = count($lines) - 1;
        $filename = self::filename('adesi_commentaires', $context['promotion']->name, $context['session']->code);
        self::log($promotionid, $sessionid, 'commentaires', $userid, $rowcount, 0, [], $filename);

        return ['success' => true, 'content' => $content, 'filename' => $filename, 'rowcount' => $rowcount, 'errors' => []];
    }

    /**
     * Shared validation for preview() and both export_*() — always
     * re-executed in full, never cached across a request boundary.
     *
     * @return array{0: array, 1: array} [preview data, internal context for the exporters]
     */
    private static function validate(int $promotionid, int $sessionid): array {
        $promotion = promotion_repository::get($promotionid);
        $session = capp_repository::get_session($sessionid);
        if (!$promotion || !$session || (int) $session->promotionid !== $promotionid) {
            throw new \moodle_exception('invalidrecord', 'error');
        }

        $entries = capp_repository::get_entries_for_session($sessionid);
        $finalised = array_values(array_filter($entries, fn($e) => $e->workflowstatus === workflow_service::STATUS_FINALISE));

        $competencies = array_values(competency_repository::get_active_for_promotion($promotionid));
        $niveaux = niveau_repository::get_all();
        $niveauxbyid = [];
        $niveauxbycode = [];
        foreach ($niveaux as $niveau) {
            $niveauxbyid[$niveau->id] = $niveau;
            $niveauxbycode[$niveau->code] = $niveau;
        }
        $defaultniveau = $niveauxbycode['COMPETENCE0'] ?? null;

        $errors = [];

        if (empty($finalised)) {
            $errors[] = get_string('adesi_error_norows', 'local_kid');
        }

        $missingcompetencyadesi = array_values(array_filter($competencies, fn($c) => trim((string) $c->codeadesi) === ''));
        foreach ($missingcompetencyadesi as $competency) {
            $errors[] = get_string('adesi_error_competency_code', 'local_kid', $competency->codecourt);
        }

        if (!$defaultniveau || trim((string) $defaultniveau->codeadesi) === '') {
            $errors[] = get_string('adesi_error_niveau_mapping', 'local_kid');
        }

        $missingcodeena = [];
        foreach ($finalised as $entry) {
            if (!codeena_resolver::resolve_for_adesi_export((int) $entry->userid, $entry->email, $entry->firstname, $entry->lastname)) {
                $missingcodeena[] = $entry->lastname . ' ' . $entry->firstname;
            }
        }
        foreach ($missingcodeena as $name) {
            $errors[] = get_string('adesi_error_codeena', 'local_kid', $name);
        }

        $exportablecompetencies = array_values(array_filter($competencies, fn($c) => trim((string) $c->codeadesi) !== ''));
        $blocked = !empty($errors);

        $preview = [
            'promotionname' => format_string($promotion->name),
            'sessioncode' => format_string($session->code),
            'niveaux_rowcount' => $blocked ? 0 : (count($finalised) * count($exportablecompetencies)),
            'commentaires_rowcount' => $blocked ? 0 : count($finalised),
            'errors' => $errors,
            'blocked' => $blocked,
            'norows' => empty($finalised),
        ];

        $context = [
            'promotion' => $promotion,
            'session' => $session,
            'finalised' => $finalised,
            'competencies' => $exportablecompetencies,
            'niveauxbyid' => $niveauxbyid,
            'defaultcodeadesi' => $defaultniveau->codeadesi ?? 'COMPETENCE0',
        ];

        return [$preview, $context];
    }

    private static function filename(string $prefix, string $promotionname, string $sessioncode): string {
        // date() rather than userdate()'s %-format emulation — the latter
        // does not reliably zero-pad month/day on every PHP/OS combination.
        $date = date('Ymd', time());
        return clean_param("{$prefix}_{$promotionname}_{$sessioncode}_{$date}.csv", PARAM_FILE);
    }

    /**
     * @param string[] $errors
     */
    private static function log(int $promotionid, int $sessionid, string $exporttype, int $userid, int $rowcount, int $errorcount, array $errors, ?string $filename): void {
        global $DB;

        $DB->insert_record('local_kid_adesi_export_log', (object) [
            'promotionid' => $promotionid,
            'sessionid' => $sessionid,
            'exporttype' => $exporttype,
            'timecreated' => time(),
            'userid' => $userid,
            'rowcount' => $rowcount,
            'errorcount' => $errorcount,
            'errordetail' => empty($errors) ? null : implode("\n", $errors),
            'filename' => $filename,
        ]);

        adesi_exported::create([
            'context' => \context_system::instance(),
            'userid' => $userid,
            'other' => ['sessionid' => $sessionid, 'exporttype' => $exporttype, 'rowcount' => $rowcount],
        ])->trigger();
    }
}
