<?php

namespace App\Support;

use App\Models\Environments\Environment;

/**
 * Qué versión de Moodle tiene un entorno, dicho de una sola manera.
 *
 * **El problema.** `environments.version` contiene dos cosas distintas según cuándo se
 * escribió por última vez:
 *
 * - Si el entorno ha sincronizado con el Manager actual, la **release** legible:
 *   `5.1.1`, `4.5.3`.
 * - Si no —plugin sin actualizar, o sitio sin señal—, lo que dejó el Manager antiguo,
 *   que era el **versiondb**: `2025100603.08`.
 *
 * Y el panel lo pintaba tal cual con «Moodle » delante, así que en el listado salía
 * `Moodle LMS 2025100603.08` y el dashboard agrupaba por «rama» partiendo por el punto,
 * inventándose cinco ramas —`2025100606`, `2025100605.02`…— que no existen. Los números
 * eran reales; lo que se decía de ellos, no.
 *
 * Aquí se decide **una vez** qué es cada valor y qué se puede afirmar. Lo que no se sabe
 * se dice que no se sabe, en lugar de enseñar un número con una etiqueta que no le
 * corresponde.
 *
 * **De dónde sale cada dato:**
 *
 * | Dato | Origen | Cuándo falta |
 * |---|---|---|
 * | release | `environments.version` | si el entorno nunca sincronizó con el código actual |
 * | versiondb | `environments.versiondb` | hasta el primer sync tras añadir la columna |
 * | build | `data.moodlerelease` | si nunca llegó la cadena cruda de Moodle |
 */
class VersionDeMoodle
{
    /**
     * ¿Este valor es un versiondb y no una release?
     *
     * Un versiondb empieza por el año en cuatro cifras y lleva al menos ocho dígitos
     * seguidos —`2025100603.08`—. Una release empieza por el major, que es de una o dos
     * cifras —`5.1.1`, `4.5.3`, `3.12`—. La longitud del primer tramo es lo que los
     * separa sin ambigüedad.
     */
    public static function esVersiondb(?string $valor): bool
    {
        return $valor !== null && preg_match('/^\d{8,}/', trim($valor)) === 1;
    }

    /**
     * La rama de una release: los dos primeros números. `5.1.1` → `5.1`.
     *
     * Devuelve null si el valor no es una release, para que quien agrupe no acabe con
     * una rama inventada a partir de un versiondb.
     */
    public static function rama(?string $version): ?string
    {
        if ($version === null || trim($version) === '' || self::esVersiondb($version)) {
            return null;
        }

        $partes = explode('.', trim($version));

        return count($partes) >= 2 ? $partes[0] . '.' . $partes[1] : $partes[0];
    }

    /**
     * El build que viene dentro de la cadena cruda de Moodle.
     *
     * `moodlerelease` llega como `5.1.1+ (Build: 20251219)`: el `+` dice que el sitio
     * lleva parches posteriores a la etiqueta y el build es la fecha exacta del código.
     */
    public static function build(?string $moodlerelease): ?string
    {
        if ($moodlerelease === null) {
            return null;
        }

        return preg_match('/Build:\s*(\d+)/i', $moodlerelease, $m) === 1 ? $m[1] : null;
    }

    /**
     * Todo lo que se sabe de la versión de un entorno.
     *
     * @return array{release: ?string, versiondb: ?string, build: ?string, rama: ?string, conParches: bool}
     */
    public static function de(Environment $entorno): array
    {
        $guardado = $entorno->version !== null ? trim((string) $entorno->version) : null;
        $guardado = $guardado === '' ? null : $guardado;

        // Si lo que hay en `version` es un versiondb, **no es una release**: se trata
        // como versiondb y la release queda desconocida. Es la diferencia entre no saber
        // y decir algo falso.
        $esLegado = self::esVersiondb($guardado);

        $cruda = $entorno->data?->moodlerelease;

        return [
            'release' => $esLegado ? null : $guardado,
            // El de su columna; y si aún no ha llegado, el que arrastraba `version`.
            'versiondb' => $entorno->versiondb ?: ($esLegado ? $guardado : null),
            'build' => self::build($cruda),
            'rama' => $esLegado ? null : self::rama($guardado),
            // El `+` de `5.1.1+`: parches por encima de la etiqueta.
            'conParches' => $cruda !== null && str_contains($cruda, '+'),
        ];
    }

    /**
     * La versión para la línea del listado: **solo la release**.
     *
     * Con el `+` de Moodle cuando lleva parches por encima de la etiqueta, porque eso sí
     * cambia lo que es. Lo demás —build y `versiondb`— vive en {@see self::detalle()},
     * que es el `title`: son datos que se consultan, no que se escanean.
     *
     * Y **sin el nombre de la plataforma**: desde el 2026-09-15 tiene columna propia con
     * su logo justo al lado, así que ponerlo aquí era decirlo dos veces en diez píxeles.
     */
    public static function etiqueta(Environment $entorno): string
    {
        $v = self::de($entorno);
        $trozos = [];

        if ($v['release'] !== null) {
            $trozos[] = $v['release'] . ($v['conParches'] ? '+' : '');
        }

        // **El `versiondb` ya no va en la línea.** Es el número interno del código de
        // Moodle —`2025100601.03`—, lo mira Sistemas una vez al mes y la release la mira
        // todo el mundo siempre. Puestos juntos y separados por un `·`, el ojo no sabía
        // cuál era cuál y la línea entera se leía como un identificador largo. Sigue en
        // `detalle()`, que es el `title`: disponible sin estorbar.

        // «release desconocida» solo cuando hay un versiondb del que decirlo. Sin
        // ningun dato no se sabe nada de la version, y decir «release desconocida» a
        // secas suena a que el resto si se sabe.
        if ($v['release'] === null && $v['versiondb'] !== null) {
            $trozos[] = 'release desconocida';
        }

        return $trozos === [] ? 'versión desconocida' : implode(' · ', $trozos);
    }

    /**
     * Lo mismo que `etiqueta()` pero entero, para un `title`.
     *
     * Aquí sí cabe el **build** —`20251219`, la fecha exacta del código que tiene
     * puesto ese Moodle— y la explicación del `+`. En la línea del listado no caben:
     * son cuatro datos para un hueco de una línea, y el que se lee de un vistazo es
     * la release.
     */
    public static function detalle(Environment $entorno): string
    {
        $v = self::de($entorno);
        $trozos = [];

        $trozos[] = $v['release'] !== null
            ? 'release ' . $v['release']
            : 'release desconocida: este Moodle no ha sincronizado con el Manager actual';

        if ($v['conParches']) {
            $trozos[] = 'con parches por encima de la etiqueta (el «+» de Moodle)';
        }

        if ($v['build'] !== null) {
            $trozos[] = 'build ' . $v['build'];
        }

        if ($v['versiondb'] !== null) {
            $trozos[] = 'versiondb ' . $v['versiondb'];
        }

        return implode(' · ', $trozos);
    }
}
