<?php

namespace App\Services\Api\Actions;

use App\Models\Environments\Data;
use App\Models\Environments\Environment;
use App\Models\Environments\EnvironmentStat;
use Carbon\Carbon;
use Illuminate\Http\Request;

/**
 * Estadísticas de un entorno: estado actual + fila del día.
 *
 * Es la acción que llama de verdad la tarea diaria del plugin
 * (`sync_stats_task` → `tip::stats()`), que envía `action = 'data'` aunque el método
 * se llame `stats()`. La acción `stats` del Manager, que era la que escribía el
 * histórico, no la llamaba nadie y se retiró en la 2.0.0; el histórico se escribe
 * aquí, que es donde llegan los datos.
 *
 * Dos escrituras con dos propósitos distintos:
 *
 *   `data`              una fila por entorno con el ÚLTIMO valor conocido. Es lo que
 *                       se mira para responder "cómo está este cliente ahora".
 *   `environment_stats` una fila por entorno y DÍA. Es lo que permite pintar la
 *                       evolución: sin ella, cada envío machaca el anterior y no hay
 *                       serie que dibujar.
 */
class DataAction implements ActionInterface
{
    public function execute(Request $request, array $payload): array
    {
        /** @var Environment $environment */
        $environment = $request->input('_environment');
        $data = $payload['data'] ?? [];

        // Buscar o crear el registro de Data para este entorno
        $environmentData = Data::where('environment_id', $environment->id)->first();

        if ($environmentData) {
            // Actualizar datos existentes
            $environmentData->update($this->mapDataFields($data));
        } else {
            // Crear nuevo registro
            $environmentData = Data::create(array_merge(
                ['environment_id' => $environment->id],
                $this->mapDataFields($data)
            ));
        }

        $fecha = $this->fechaDeLaFoto($payload);

        // Si el mismo día llegan dos envíos —una tarea que se repite, un `sync`
        // manual— el último gana y no se duplica la fila.
        //
        // La búsqueda va con `whereDate` y NO con `updateOrCreate(['date' => $fecha])`:
        // el modelo castea `date`, así que el valor guardado lleva hora
        // ("2026-08-27 00:00:00") mientras la comparación usa "2026-08-27". En MariaDB
        // la columna es DATE y cuela; en sqlite no casa, intenta insertar otra vez y
        // salta la clave única —o sea un 500 al Moodle en el segundo envío del día—.
        // `whereDate` lo compila cada motor a su manera y funciona en los dos.
        $existente = EnvironmentStat::where('environment_id', $environment->id)
            ->whereDate('date', $fecha)
            ->first();

        // El payload COMPLETO, no el mapeado: el histórico tiene que poder responder
        // preguntas que hoy no nos hacemos, y los campos para los que `data` no tiene
        // columna se perderían para siempre.
        if ($existente) {
            $existente->update(['data' => $data]);
        } else {
            EnvironmentStat::create([
                'environment_id' => $environment->id,
                'date' => $fecha,
                'data' => $data,
            ]);
        }

        return [
            'received' => true,
            'environment_id' => $environment->id,
            // Clave nueva (nunca se quitan claves, ver la norma de
            // retrocompatibilidad): deja ver qué día se ha guardado la foto.
            'date' => $fecha,
        ];
    }

    /**
     * Día al que corresponde la foto.
     *
     * El plugin envía `date` en formato `Y-m-d` (`userdate(time(), '%Y-%m-%d')`, o sea
     * la fecha del Moodle, no la nuestra). Si no viene —una versión antigua, una
     * llamada manual— se usa hoy: es mejor una fila con la fecha de recepción que
     * ninguna fila.
     */
    private function fechaDeLaFoto(array $payload): string
    {
        $enviada = $payload['date'] ?? null;

        if (is_string($enviada) && $enviada !== '') {
            try {
                // **`parse()` y no `createFromFormat('Y-m-d', …)`**: el plugin manda el
                // día sin cero delante (`2026-09-4`) y con el formato estricto esto
                // caía al `catch`, así que la foto se guardaba con la fecha de HOY en
                // lugar de la del envío. Con envíos de madrugada eso mete la foto en el
                // día siguiente. Ver MGR-086.
                return Carbon::parse($enviada)->format('Y-m-d');
            } catch (\Throwable) {
                // Fecha ilegible: no es motivo para perder la foto del día.
            }
        }

        return now()->format('Y-m-d');
    }

    /**
     * Mapea los campos recibidos a los campos de la tabla data
     * 
     * @param array $data
     * @return array
     */
    private function mapDataFields(array $data): array
    {
        // Mapeo de campos posibles del payload a campos de la tabla
        $mapping = [
            'policyagreed' => 'policyagreed',
            'language' => 'language',
            'countrycode' => 'countrycode',
            'privacy' => 'privacy',
            'contactemail' => 'contactemail',
            // `contactable` retirado del mapeo el 2026-09-04: el equipo del plugin
            // comprobó que **no existe** en el registro de sitio de Moodle 4.5 ni 5.1
            // —ni en `FORM_FIELDS` ni en lo que calcula `registration::get_site_info()`—,
            // así que solo se podría rellenar inventándolo. La columna se queda en la
            // tabla (borrarla no compensa en la tabla que más crece), pero no se mapea
            // ni se enseña. Ver MGR-077.
            'emailalert' => 'emailalert',
            'emailalertemail' => 'emailalertemail',
            'commnews' => 'commnews',
            'commnewsemail' => 'commnewsemail',
            'contactname' => 'contactname',
            'description' => 'description',
            'imageurl' => 'imageurl',
            'contactphone' => 'contactphone',
            'regioncode' => 'regioncode',
            'geolocation' => 'geolocation',
            'street' => 'street',
            'courses' => 'courses',
            'users' => 'users',
            'activeusers' => 'activeusers',
            'enrolments' => 'enrolments',
            'posts' => 'posts',
            'questions' => 'questions',
            'resources' => 'resources',
            'badges' => 'badges',
            'issuedbadges' => 'issuedbadges',
            'participantnumberaverage' => 'participantnumberaverage',
            'activeparticipantnumberaverage' => 'activeparticipantnumberaverage',
            'modulenumberaverage' => 'modulenumberaverage',
            'moodlerelease' => 'moodlerelease',
            'url' => 'url',
            'mobileservicesenabled' => 'mobileservicesenabled',
            'mobilenotificationsenabled' => 'mobilenotificationsenabled',
            'registereduserdevices' => 'registereduserdevices',
            'registeredactiveuserdevices' => 'registeredactiveuserdevices',
            'analyticsenabledmodels' => 'analyticsenabledmodels',
            'analyticspredictions' => 'analyticspredictions',
            'analyticsactions' => 'analyticsactions',
            'analyticsactionsnotuseful' => 'analyticsactionsnotuseful',
            'availableupdatesfetch' => 'availableupdatesfetch',
        ];

        $mapped = [];

        foreach ($mapping as $key => $field) {
            // **`array_key_exists` y no `isset`.** `isset()` descarta los nulos, así
            // que un campo que el sitio manda vacío no sobrescribía nada y la columna
            // se quedaba con el valor de la última vez que vino con algo: la ficha del
            // entorno llegó a enseñar un correo de contacto guardado en mayo como si
            // fuera el de hoy.
            //
            // **Viene vacío es una respuesta**: el sitio dice que no tiene ese dato, y
            // hay que hacerle caso. Lo que no se toca es lo que **no viene**: eso no es
            // el sitio diciendo «vacío», es una versión del plugin que no manda ese
            // campo, y borrar el dato bueno de un cliente por una bajada de versión
            // sería perder información. Misma decisión que el `versiondb` en
            // `SyncAction`.
            if (array_key_exists($key, $data)) {
                $mapped[$field] = $data[$key];
            }
        }

        return $mapped;
    }
}

