<?php

namespace App\Http\Controllers\Api\V1;

use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\MoodleApiRequest;
use App\Services\Api\ActionResolver;
use App\Services\Api\ApiResponse;
use App\Services\Api\Exceptions\EnvironmentNotFoundException;
use App\Services\Api\Exceptions\FeaturesException;
use App\Services\Api\Exceptions\SetupException;
use App\Services\Api\Exceptions\ScssException;
use App\Services\Api\Exceptions\JsException;
use App\Services\Api\Exceptions\TutorialsException;
use App\Services\Api\Exceptions\ResourcesException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class MoodleApiController extends Controller
{
    public function __construct(
        private ActionResolver $actionResolver
    ) {
    }

    /**
     * Maneja todas las peticiones POST a la API
     *
     * @param MoodleApiRequest $request
     * @return JsonResponse
     */
    public function handle(MoodleApiRequest $request): JsonResponse
    {
        try {
            $action = $request->input('action');

            // Forzar validación del FormRequest (mantiene rechazos por
            // peticiones malformadas) pero pasar a las Actions el body JSON
            // ORIGINAL, no `validated()`. `validated()` descarta cualquier
            // campo sin regla (p. ej. `site.key`, `site.availableupdates`,
            // `site.availableupdatesfetch`) y mutila el payload antes de
            // llegar a las Actions. Ver fixes/PRODSECU-152.
            $request->validated();
            $payload = $request->json()->all() ?: $request->all();

            // Ejecutar la acción correspondiente
            $data = $this->actionResolver->resolve($action, $request, $payload);

            // Retornar respuesta exitosa
            return ApiResponse::success($action, $data);

        } catch (SetupException $e) {
            // Error específico de setup con códigos personalizados
            return ApiResponse::error(
                $request->input('action', 'setup'),
                $e->getMessage(),
                $e->getApiCode(),
                [],
                $e->getHttpStatus()
            );

        } catch (ScssException $e) {
            // Error específico de scss con códigos personalizados
            return ApiResponse::error(
                $request->input('action', 'scss'),
                $e->getMessage(),
                $e->getApiCode(),
                [],
                $e->getHttpStatus()
            );

        } catch (JsException $e) {
            // Error específico de js con códigos personalizados
            return ApiResponse::error(
                $request->input('action', 'js'),
                $e->getMessage(),
                $e->getApiCode(),
                [],
                $e->getHttpStatus()
            );

        } catch (TutorialsException $e) {
            // Error específico de tutorials con códigos personalizados
            return ApiResponse::error(
                $request->input('action', 'tutorials'),
                $e->getMessage(),
                $e->getApiCode(),
                [],
                $e->getHttpStatus()
            );

        } catch (ResourcesException $e) {
            // Error específico de resources con códigos personalizados
            return ApiResponse::error(
                $request->input('action', 'resources'),
                $e->getMessage(),
                $e->getApiCode(),
                [],
                $e->getHttpStatus()
            );

        } catch (FeaturesException $e) {
            // `features` lanzaba ScssException con códigos 3xxx, de la familia de
            // scss/setup. Su familia es la 5xxx (ver error-codes.md), que es además
            // la que ya usa el middleware para esta acción con 5001.
            return ApiResponse::error(
                $request->input('action', 'features'),
                $e->getMessage(),
                $e->getApiCode(),
                [],
                $e->getHttpStatus()
            );

        } catch (EnvironmentNotFoundException $e) {
            // Error de DOMINIO, no avería: el dominio que llama no está dado de alta
            // en este token. El mensaje se mantiene porque el plugin lo escribe en su
            // propio log y es lo que le dice al administrador del Moodle qué pasa.
            return ApiResponse::error(
                $request->input('action', 'unknown'),
                $e->getMessage(),
                $e->getApiCode(),
                [],
                $e->getHttpStatus()
            );

        } catch (UnauthorizedHttpException $e) {
            // Lanzada por el framework: el mensaje puede traer detalle interno.
            return $this->errorInterno($request, $e, 401, 'No autorizado.');

        } catch (NotFoundHttpException $e) {
            // Ídem: el 404 automático de Laravel dice qué modelo y qué id buscaba
            // ("No query results for model [App\Models\...] 5").
            return $this->errorInterno($request, $e, 404, 'Recurso no encontrado.');

        } catch (\InvalidArgumentException $e) {
            // ÚNICA excepción genérica cuyo mensaje se mantiene: siempre se lanza a
            // propósito para decirle al plugin qué ha enviado mal ("Acción 'x' no
            // reconocida", "El plugin debe tener al menos name o component"). Es
            // culpa del cliente y sin el texto no sabe qué corregir.
            return ApiResponse::error(
                $request->input('action', 'unknown'),
                $e->getMessage(),
                400
            );

        } catch (\Throwable $e) {
            // Todo lo demás: RuntimeException, errores de base de datos, TypeError…
            // Se captura `\Throwable` y no `\Exception` para que un `\Error` de PHP
            // (TypeError, DivisionByZeroError) también salga en el sobre de la API
            // en vez de en la página de error de Laravel.
            return $this->errorInterno($request, $e, 500, 'Error interno del servidor.');
        }
    }

    /**
     * Respuesta de error que NO cuenta lo que ha fallado por dentro.
     *
     * El mensaje que se devolvía antes era `$e->getMessage()` tal cual, y eso saca a
     * un cliente HTTP cualquiera lo que la excepción llevara dentro: nombres de
     * clase y namespaces ("La clase de acción 'App\Services\Api\Actions\XAction' no
     * existe"), errores de MySQL con tabla y columna, rutas del servidor. Ver
     * known-issues MGR-007.
     *
     * A cambio de quitar el detalle se da algo mejor para diagnosticar: el
     * `request_uuid` de la petición. Va en el texto —para que se lea en el log del
     * Moodle— y en `data.request_uuid` —para que el plugin lo pueda tratar—. Con esa
     * referencia, soporte filtra en `/api-logs` por UUID y ve la petición completa.
     *
     * El detalle real va a dos sitios: al canal `api` (`storage/logs/api.log`) con
     * contexto de la petición, y a `report()`, que es el camino normal de errores de
     * Laravel. No se pierde nada; deja de viajar por la red.
     */
    private function errorInterno(Request $request, \Throwable $e, int $httpStatus, string $mensaje): JsonResponse
    {
        $accion = (string) $request->input('action', 'unknown');
        $referencia = (string) $request->attributes->get('api_request_uuid', '');

        Log::channel('api')->error('Excepción no controlada en la API', [
            'request_uuid' => $referencia !== '' ? $referencia : null,
            'action' => $accion,
            'host' => $request->input('host'),
            'ip' => $request->ip(),
            'exception' => $e::class,
            'message' => $e->getMessage(),
            'at' => $e->getFile() . ':' . $e->getLine(),
        ]);

        report($e);

        return ApiResponse::error(
            $accion,
            $referencia !== '' ? $mensaje . ' Referencia: ' . $referencia : $mensaje,
            $httpStatus,
            $referencia !== '' ? ['request_uuid' => $referencia] : [],
            $httpStatus
        );
    }
}

