<?php

namespace App\Http\Controllers;

use App\Models\Curso;
use App\Models\Horario;
use App\Models\Instituto;
use App\Models\Matricula;
use App\Models\MatriculaCurso;
use App\Models\Periodo;
use App\Models\PlanEstudio;
use App\Models\Postulante;
use App\Models\Semestre;
use App\Services\MoodleService;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response as HttpResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Inertia\Inertia;
use Inertia\Response;

class MatriculaController extends Controller
{
    /**
     * Límite máximo de créditos permitidos por periodo lectivo.
     */
    private const LIMITE_MAX_CREDITOS = 26.00;

    protected MoodleService $moodleService;

    public function __construct(MoodleService $moodleService)
    {
        $this->moodleService = $moodleService;
    }

    /**
     * Vista principal de matrículas (Carga inicial síncrona).
     */
    public function index(): Response
    {
        $matriculas = Matricula::with(['postulante', 'planEstudio', 'periodo', 'semestre'])
            ->orderBy('id', 'desc')
            ->paginate(10);

        return Inertia::render('Matriculas/Index', [
            'matriculas' => $matriculas,
            'periodos'   => Periodo::select('id', 'nombre')->get(),
            'semestres'  => Semestre::select('id', 'nombre')->get(),
            'estados'    => ['Pendiente', 'Matriculado', 'Retirado', 'Convalidado', 'Repitencia'],
            'filtros'    => (object) [],
        ]);
    }

    /**
     * Filtrado dinámico vía AJAX (Peticiones asíncronas de Axios).
     */
    public function filtrar(Request $request): JsonResponse
    {
        $buscar     = trim((string) $request->input('buscar'));
        $periodoId  = $request->input('periodo_id');
        $semestreId = $request->input('semestre_id');
        $estado     = $request->input('estado');

        $query = Matricula::with(['postulante', 'planEstudio', 'periodo', 'semestre']);

        if ($buscar !== '') {
            $query->where(function ($q) use ($buscar) {
                $q->where('codigo_matricula', 'like', "%{$buscar}%")
                    ->orWhereHas('postulante', function ($sub) use ($buscar) {
                        $sub->where('dni', 'like', "%{$buscar}%")
                            ->orWhere('nombres', 'like', "%{$buscar}%")
                            ->orWhere('apellidos', 'like', "%{$buscar}%");
                    });
            });
        }

        if (! blank($periodoId)) {
            $query->where('periodo_id', $periodoId);
        }
        if (! blank($semestreId)) {
            $query->where('semestre_id', $semestreId);
        }
        if (! blank($estado)) {
            $query->where('estado', $estado);
        }

        $matriculas = $query->orderBy('id', 'desc')->paginate(10);

        return response()->json(['matriculas' => $matriculas]);
    }

    /**
     * Muestra el formulario para crear una nueva matrícula.
     */
    public function create(Request $request): Response
    {
        $periodos    = Periodo::select('id', 'nombre', 'activo')->where('activo', 1)->get();
        $semestres   = Semestre::select('id', 'nombre')->get();
        $planes      = PlanEstudio::select('id', 'nombre', 'codigo')->get();
        $estudiantes = Postulante::select('id_postulante', 'nombres', 'apellidos', 'dni', 'grado')
            ->orderBy('apellidos')
            ->get();

        $periodoSeleccionado = $request->input('periodo_id') ?? $periodos->first()?->id;

        $horariosDisponibles = Horario::with([
            'curso:id,nombre',
            'seccion:id,nombre',
            'turno:id,nombre',
            'docente:id,nombre,apellido',
        ])
        ->when($periodoSeleccionado, fn ($q) => $q->where('id_periodo', $periodoSeleccionado))
        ->get()
        ->map(fn ($horario) => [
            'id'              => $horario->id,
            'curso_id'        => $horario->id_curso,
            'curso_nombre'    => $horario->curso?->nombre,
            'curso_codigo'    => 'CUR-' . str_pad($horario->id_curso, 3, '0', STR_PAD_LEFT),
            'seccion'         => $horario->seccion?->nombre,
            'turno'           => $horario->turno?->nombre,
            'docente'         => $horario->docente ? ($horario->docente->nombre . ' ' . $horario->docente->apellido) : 'Por asignar',
            'detalle_horario' => "{$horario->dia}: {$horario->hora_inicio} - {$horario->hora_fin}",
            'capacidad'       => $horario->capacidad,
        ]);

        return Inertia::render('Matriculas/Create', [
            'estudiantes'      => $estudiantes,
            'planes'           => $planes,
            'periodos'         => $periodos,
            'semestres'        => $semestres,
            'ofertaAcademicas' => $horariosDisponibles,
            'filtroPeriodo'    => $periodoSeleccionado,
        ]);
    }

    /**
     * Endpoint AJAX para cargar horarios por semestre, plan y periodo.
     */
    public function obtenerHorariosPorSemestre(Request $request): JsonResponse
    {
        $request->validate([
            'periodo_id'      => 'required|integer|exists:periodos,id',
            'semestre_id'     => 'required|integer|exists:semestres,id',
            'plan_estudio_id' => 'required|integer|exists:planes_estudio,id',
        ]);

        $periodoId     = $request->input('periodo_id');
        $semestreId    = $request->input('semestre_id');
        $planEstudioId = $request->input('plan_estudio_id');

        $horarios = Horario::with([
            'curso:id,nombre,semestre_id',
            'seccion:id,nombre',
            'turno:id,nombre',
            'docente:id,nombre,apellido',
        ])
        ->where('id_periodo', $periodoId)
        ->whereHas('curso', function ($query) use ($semestreId, $planEstudioId) {
            $query->where('semestre_id', $semestreId)
                ->whereHas('planesEstudio', fn ($pQuery) => $pQuery->where('plan_estudio_id', $planEstudioId));
        })
        ->get()
        ->map(fn ($horario) => [
            'id'              => $horario->id,
            'curso_id'        => $horario->id_curso,
            'curso_nombre'    => $horario->curso?->nombre,
            'curso_codigo'    => 'CUR-' . str_pad($horario->id_curso, 3, '0', STR_PAD_LEFT),
            'seccion'         => $horario->seccion?->nombre,
            'turno'           => $horario->turno?->nombre,
            'docente'         => $horario->docente ? ($horario->docente->nombre . ' ' . $horario->docente->apellido) : 'Por asignar',
            'detalle_horario' => "{$horario->dia}: {$horario->hora_inicio} - {$horario->hora_fin}",
            'capacidad'       => $horario->capacidad,
        ]);

        return response()->json(['horarios' => $horarios]);
    }

    /**
     * Verifica si un postulante/estudiante ya tiene matrícula en el periodo.
     */
    public function verificarMatriculaEstudiante(Request $request): JsonResponse
    {
        $postulanteId = $request->input('postulante_id');
        $periodoId    = $request->input('periodo_id');

        $matricula = Matricula::where('postulante_id', $postulanteId)
            ->where('periodo_id', $periodoId)
            ->first();

        if ($matricula) {
            return response()->json([
                'existe'          => true,
                'semestre_id'     => $matricula->semestre_id,
                'plan_estudio_id' => $matricula->plan_estudio_id,
            ]);
        }

        return response()->json(['existe' => false]);
    }

    /**
     * Registra o complementa la matrícula y la sincroniza con Moodle.
     */
    public function store(Request $request): RedirectResponse
    {
        $datos = $request->validate([
            'postulante_id'                     => 'required|integer|exists:postulantes,id_postulante',
            'plan_estudio_id'                   => 'required|integer|exists:planes_estudio,id',
            'periodo_id'                        => 'required|integer|exists:periodos,id',
            'semestre_id'                       => 'required|integer|exists:semestres,id',
            'fecha_matricula'                   => 'required|date',
            'cursos_seleccionados'              => 'required|array|min:1',
            'cursos_seleccionados.*.horario_id' => 'required|integer|exists:horarios,id',
            'cursos_seleccionados.*.estado'     => 'nullable|string|in:Inscrito,Repitencia,Cargo,Convalidado',
        ], [
            'cursos_seleccionados.required' => 'Debe seleccionar al menos una asignatura para la matrícula.',
            'cursos_seleccionados.min'      => 'Debe seleccionar al menos una asignatura para la matrícula.',
        ]);

        try {
            $cursosParaSincronizarMoodle = [];
            $postulante = null;

            DB::transaction(function () use ($datos, &$cursosParaSincronizarMoodle, &$postulante) {
                // 1. Validar que el periodo lectivo se encuentre activo
                $this->validarPeriodoActivo((int) $datos['periodo_id']);

                // 2. Cargar todos los horarios enviados con sus relaciones
                $horariosIds = collect($datos['cursos_seleccionados'])->pluck('horario_id')->unique()->toArray();
                $horariosCargados = Horario::with(['curso', 'seccion'])->whereIn('id', $horariosIds)->get()->keyBy('id');

                // 3. Validaciones académicas estrictas
                $this->validarSeccionUnicaPorCurso($horariosIds, $horariosCargados);
                $this->validarCruceHorarioEstudiante($horariosCargados);
                $this->validarLimiteCreditos($horariosCargados);

                // 4. Obtener o inicializar registro de matrícula
                $matricula = Matricula::firstOrNew([
                    'postulante_id' => $datos['postulante_id'],
                    'periodo_id'    => $datos['periodo_id'],
                ]);

                // 5. Validar aforo por horario/sección
                $this->validarAforoHorarios($datos['cursos_seleccionados'], $horariosCargados, $matricula);

                $matricula->plan_estudio_id = $datos['plan_estudio_id'];
                $matricula->semestre_id     = $datos['semestre_id'];
                $matricula->estado          = 'Matriculado';
                $matricula->fecha_matricula = $datos['fecha_matricula'];
                $matricula->save();

                // Generar correlativo oficial si es nueva matrícula
                if (empty($matricula->codigo_matricula)) {
                    $añoActual = date('Y');
                    $matricula->update([
                        'codigo_matricula' => 'MAT-' . $añoActual . '-' . str_pad($matricula->id, 5, '0', STR_PAD_LEFT),
                    ]);
                }

                // 6. Registrar asignaturas en la tabla pivot
                foreach ($datos['cursos_seleccionados'] as $item) {
                    $horario = $horariosCargados->get($item['horario_id']);
                    if ($horario) {
                        $matricula->cursosMatriculados()->updateOrCreate(
                            ['horario_id' => $horario->id],
                            [
                                'curso_id' => $horario->id_curso,
                                'estado'   => $item['estado'] ?? 'Inscrito',
                            ]
                        );

                        $cursosParaSincronizarMoodle[] = $horario;
                    }
                }

                // 7. Actualizar condición del postulante a Estudiante
                $postulante = Postulante::find($datos['postulante_id']);
                if ($postulante && $postulante->grado !== 'Estudiante') {
                    $postulante->update(['grado' => 'Estudiante']);
                }
            });

            // =========================================================================
            // 8. SINCRONIZACIÓN CON MOODLE (MATRÍCULA + GRUPOS)
            // =========================================================================
            if ($postulante) {
                $moodleUserId = $this->obtenerMoodleUserIdEstudiante($postulante);

                if ($moodleUserId) {
                    foreach ($cursosParaSincronizarMoodle as $horario) {
                        $this->matricularEstudianteEnMoodle($moodleUserId, $horario);
                    }
                }
            }

            return to_route('matriculas.index')->with('success', 'Matrícula y sincronización con el Aula Virtual procesadas con éxito.');
        } catch (\Exception $e) {
            return back()->withErrors(['error' => $e->getMessage()]);
        }
    }

    /**
     * Muestra el formulario para editar una matrícula existente.
     */
    public function edit(int $id): Response
    {
        $matricula = Matricula::with(['cursosMatriculados.horario', 'postulante'])->findOrFail($id);

        $periodos  = Periodo::select('id', 'nombre', 'activo')->get();
        $semestres = Semestre::select('id', 'nombre')->get();
        $planes    = PlanEstudio::select('id', 'nombre', 'codigo')->get();
        $estados   = ['Pendiente', 'Matriculado', 'Retirado', 'Convalidado', 'Repitencia'];

        $horariosDisponibles = Horario::with([
            'curso:id,nombre,semestre_id',
            'seccion:id,nombre',
            'turno:id,nombre',
            'docente:id,nombre,apellido',
        ])
        ->where('id_periodo', $matricula->periodo_id)
        ->whereHas('curso', function ($query) use ($matricula) {
            $query->where('semestre_id', $matricula->semestre_id)
                ->whereHas('planesEstudio', fn ($pQuery) => $pQuery->where('plan_estudio_id', $matricula->plan_estudio_id));
        })
        ->get()
        ->map(fn ($horario) => [
            'id'              => $horario->id,
            'curso_id'        => $horario->id_curso,
            'curso_nombre'    => $horario->curso?->nombre,
            'curso_codigo'    => 'CUR-' . str_pad($horario->id_curso, 3, '0', STR_PAD_LEFT),
            'seccion'         => $horario->seccion?->nombre,
            'turno'           => $horario->turno?->nombre,
            'docente'         => $horario->docente ? ($horario->docente->nombre . ' ' . $horario->docente->apellido) : 'Por asignar',
            'detalle_horario' => "{$horario->dia}: {$horario->hora_inicio} - {$horario->hora_fin}",
            'capacidad'       => $horario->capacidad,
        ]);

        $cursosActuales = $matricula->cursosMatriculados->map(fn ($det) => [
            'horario_id' => $det->horario_id,
            'estado'     => $det->estado,
        ]);

        return Inertia::render('Matriculas/Edit', [
            'matricula'        => $matricula,
            'planes'           => $planes,
            'periodos'         => $periodos,
            'semestres'        => $semestres,
            'estados'          => $estados,
            'ofertaAcademicas' => $horariosDisponibles,
            'cursosActuales'   => $cursosActuales,
        ]);
    }

    /**
     * Actualiza la matrícula y sincroniza altas/bajas con Moodle.
     */
    public function update(Request $request, int $id): RedirectResponse
    {
        $matricula = Matricula::with(['cursosMatriculados.horario.curso', 'cursosMatriculados.horario.seccion', 'postulante'])->findOrFail($id);

        $datos = $request->validate([
            'plan_estudio_id'                   => 'required|integer|exists:planes_estudio,id',
            'periodo_id'                        => 'required|integer|exists:periodos,id',
            'semestre_id'                       => 'required|integer|exists:semestres,id',
            'estado'                            => 'required|string|in:Pendiente,Matriculado,Retirado,Convalidado,Repitencia',
            'fecha_matricula'                   => 'required|date',
            'cursos_seleccionados'              => 'required|array|min:1',
            'cursos_seleccionados.*.horario_id' => 'required|integer|exists:horarios,id',
            'cursos_seleccionados.*.estado'     => 'nullable|string',
        ], [
            'cursos_seleccionados.required' => 'Debe mantener al menos una asignatura seleccionada.',
            'cursos_seleccionados.min'      => 'Debe mantener al menos una asignatura seleccionada.',
        ]);

        try {
            $horariosViejos = $matricula->cursosMatriculados->pluck('horario')->filter();
            $horariosNuevosParaMatricular = [];
            $horariosParaDesmatricular = [];

            DB::transaction(function () use ($datos, $matricula, $horariosViejos, &$horariosNuevosParaMatricular, &$horariosParaDesmatricular) {
                // 1. Cargar horarios seleccionados
                $horariosIds = collect($datos['cursos_seleccionados'])->pluck('horario_id')->unique()->toArray();
                $horariosCargados = Horario::with(['curso', 'seccion'])->whereIn('id', $horariosIds)->get()->keyBy('id');

                // 2. Validaciones académicas
                $this->validarSeccionUnicaPorCurso($horariosIds, $horariosCargados);
                $this->validarCruceHorarioEstudiante($horariosCargados);
                $this->validarLimiteCreditos($horariosCargados);
                $this->validarAforoHorarios($datos['cursos_seleccionados'], $horariosCargados, $matricula);

                // 3. Identificar cursos eliminados y nuevos
                $horariosViejosIds = $horariosViejos->pluck('id')->toArray();
                
                $horariosEliminadosIds = array_diff($horariosViejosIds, $horariosIds);
                $horariosParaDesmatricular = $horariosViejos->whereIn('id', $horariosEliminadosIds);

                $horariosAgregadosIds = array_diff($horariosIds, $horariosViejosIds);
                $horariosNuevosParaMatricular = $horariosCargados->whereIn('id', $horariosAgregadosIds);

                // 4. Actualizar cabecera de matrícula
                $matricula->update([
                    'plan_estudio_id' => $datos['plan_estudio_id'],
                    'periodo_id'      => $datos['periodo_id'],
                    'semestre_id'     => $datos['semestre_id'],
                    'estado'          => $datos['estado'],
                    'fecha_matricula' => $datos['fecha_matricula'],
                ]);

                // 5. Sincronizar asignaturas en BD local
                $matricula->cursosMatriculados()->whereNotIn('horario_id', $horariosIds)->delete();

                foreach ($datos['cursos_seleccionados'] as $item) {
                    $horario = $horariosCargados->get($item['horario_id']);
                    if ($horario) {
                        $matricula->cursosMatriculados()->updateOrCreate(
                            ['horario_id' => $horario->id],
                            [
                                'curso_id' => $horario->id_curso,
                                'estado'   => $item['estado'] ?? 'Inscrito',
                            ]
                        );
                    }
                }
            });

            // =========================================================================
            // 6. SINCRONIZACIÓN CON MOODLE (ALTAS, BAJAS O RETIRO COMPLETO)
            // =========================================================================
            $postulante = $matricula->postulante;
            if ($postulante) {
                $moodleUserId = $this->obtenerMoodleUserIdEstudiante($postulante);

                if ($moodleUserId) {
                    // Si el estado general cambió a Retirado, desmatricular de todos los cursos
                    if ($datos['estado'] === 'Retirado') {
                        foreach ($horariosViejos as $horario) {
                            $this->desmatricularEstudianteEnMoodle($moodleUserId, $horario);
                        }
                    } else {
                        // Desmatricular materias que el alumno quitó
                        foreach ($horariosParaDesmatricular as $horario) {
                            $this->desmatricularEstudianteEnMoodle($moodleUserId, $horario);
                        }

                        // Matricular nuevas materias agregadas
                        foreach ($horariosNuevosParaMatricular as $horario) {
                            $this->matricularEstudianteEnMoodle($moodleUserId, $horario);
                        }
                    }
                }
            }

            return to_route('matriculas.index')->with('success', 'Matrícula y Aula Virtual sincronizadas con éxito.');
        } catch (\Exception $e) {
            return back()->withErrors(['error' => $e->getMessage()]);
        }
    }

    /**
     * Elimina una matrícula y desmatricula al estudiante de Moodle.
     */
    public function destroy(int $id): RedirectResponse
    {
        try {
            $matricula = Matricula::with(['cursosMatriculados.horario.curso', 'cursosMatriculados.horario.seccion', 'postulante'])->findOrFail($id);

            $horarios = $matricula->cursosMatriculados->pluck('horario')->filter();
            $postulante = $matricula->postulante;

            // 1. Desmatricular de Moodle en cada curso asociado
            if ($postulante) {
                $moodleUserId = $this->obtenerMoodleUserIdEstudiante($postulante);
                if ($moodleUserId) {
                    foreach ($horarios as $horario) {
                        $this->desmatricularEstudianteEnMoodle($moodleUserId, $horario);
                    }
                }
            }

            // 2. Eliminar registros de la base de datos local
            DB::transaction(function () use ($matricula) {
                $matricula->cursosMatriculados()->delete();
                $matricula->delete();
            });

            return redirect()->route('matriculas.index')->with('success', 'La matrícula fue removida del sistema y desmatriculada del Aula Virtual.');
        } catch (\Exception $e) {
            return redirect()->route('matriculas.index')->with('error', 'No se pudo eliminar la matrícula: ' . $e->getMessage());
        }
    }

    /**
     * Muestra la ficha oficial de matrícula en formato PDF.
     */
    public function verFicha(int $id): HttpResponse
    {
        $matricula = Matricula::with([
            'postulante',
            'planEstudio',
            'periodo',
            'semestre',
            'cursosMatriculados.curso',
            'cursosMatriculados.horario.seccion',
            'cursosMatriculados.horario.turno',
            'cursosMatriculados.horario.docente',
        ])->findOrFail($id);

        $cursosUnicos = $matricula->cursosMatriculados
            ->groupBy('curso_id')
            ->map(function ($items) {
                $primerItem  = $items->first();
                $horarioBase = $primerItem->horario;

                $horariosFormateados = $items->map(function ($det) {
                    $h = $det->horario;
                    return $h ? "{$h->dia}: {$h->hora_inicio} - {$h->hora_fin}" : null;
                })->filter()->unique()->implode(' / ');

                return [
                    'curso_nombre' => $primerItem->curso->nombre ?? '---',
                    'seccion'      => $horarioBase?->seccion?->nombre ?? '---',
                    'turno'        => $horarioBase?->turno?->nombre ?? '---',
                    'docente'      => $horarioBase?->docente
                        ? "{$horarioBase->docente->nombre} {$horarioBase->docente->apellido}"
                        : 'Por asignar',
                    'horarios'     => ! empty($horariosFormateados) ? $horariosFormateados : '---',
                    'estado'       => $primerItem->estado ?? 'Inscrito',
                ];
            })->values();

        $instituto = Instituto::with('distrito.provincia.departamento')->first();

        $imagenBase64 = null;
        if ($instituto && ! empty($instituto->logo) && file_exists(public_path('storage/' . $instituto->logo))) {
            $path = public_path('storage/' . $instituto->logo);
            $type = pathinfo($path, PATHINFO_EXTENSION);
            $data = file_get_contents($path);
            $imagenBase64 = 'data:image/' . $type . ';base64,' . base64_encode($data);
        }

        $pdf = Pdf::loadView('pdf.ficha_matricula', [
            'matricula'    => $matricula,
            'cursosUnicos' => $cursosUnicos,
            'instituto'    => $instituto,
            'imagenBase64' => $imagenBase64,
        ]);

        return $pdf->stream('Ficha_Matricula_' . ($matricula->codigo_matricula ?? $id) . '.pdf');
    }

    /**
     * =========================================================================
     * MÉTODOS AUXILIARES DE INTEGRACIÓN CON MOODLE
     * =========================================================================
     */

    /**
     * Obtiene el identificador numérico del estudiante en Moodle.
     */
    private function obtenerMoodleUserIdEstudiante(Postulante $postulante): ?int
    {
        $dni = $postulante->dni 
            ?? $postulante->numero_documento 
            ?? $postulante->persona?->numero_documento 
            ?? null;

        if ($postulante->moodle_user_id) {
            return (int) $postulante->moodle_user_id;
        }

        if ($dni) {
            try {
                $moodleUser = $this->moodleService->obtenerUsuarioPorCampo('username', (string) $dni);
                if ($moodleUser && isset($moodleUser['id'])) {
                    $moodleId = (int) $moodleUser['id'];
                    $postulante->update(['moodle_user_id' => $moodleId]);
                    return $moodleId;
                }
            } catch (\Throwable $e) {
                Log::error("Error buscando usuario Moodle ({$dni}): " . $e->getMessage());
            }
        }

        return null;
    }

    /**
     * Matricula al estudiante en el curso y lo agrega al grupo correspondiente en Moodle.
     */
    private function matricularEstudianteEnMoodle(int $moodleUserId, Horario $horario): void
    {
        $moodleCourseId = $horario->curso?->moodle_course_id;
        $moodleGroupId  = $horario->moodle_group_id ?? $horario->seccion?->moodle_group_id ?? null;

        if ($moodleCourseId) {
            try {
                // Matricular con rol de estudiante (roleid = 5)
                $this->moodleService->matricularUsuario($moodleUserId, (int) $moodleCourseId, 5);

                // Asignar al grupo/sección
                if ($moodleGroupId) {
                    $this->moodleService->agregarUsuarioAGrupo((int) $moodleGroupId, $moodleUserId);
                }
            } catch (\Throwable $e) {
                Log::error("Error matriculando estudiante {$moodleUserId} en curso {$moodleCourseId}: " . $e->getMessage());
            }
        }
    }

    /**
     * Desmatricula al estudiante del curso y lo retira de su grupo en Moodle.
     */
    private function desmatricularEstudianteEnMoodle(int $moodleUserId, Horario $horario): void
    {
        $moodleCourseId = $horario->curso?->moodle_course_id;
        $moodleGroupId  = $horario->moodle_group_id ?? $horario->seccion?->moodle_group_id ?? null;

        if ($moodleCourseId) {
            try {
                // 1. Quitar del grupo si corresponde
                if ($moodleGroupId) {
                    $this->moodleService->eliminarUsuarioDeGrupo((int) $moodleGroupId, $moodleUserId);
                }

                // 2. Desmatricular del curso (roleid = 5)
                $this->moodleService->desmatricularUsuario($moodleUserId, (int) $moodleCourseId, 5);
            } catch (\Throwable $e) {
                Log::error("Error desmatriculando estudiante {$moodleUserId} de curso {$moodleCourseId}: " . $e->getMessage());
            }
        }
    }

    /**
     * =========================================================================
     * REGLAS DE VALIDACIÓN ACADÉMICA
     * =========================================================================
     */

    private function validarSeccionUnicaPorCurso(array $horariosIds, $horariosCargados): void
    {
        $seccionesPorCurso = [];
        foreach ($horariosIds as $hId) {
            $horario = $horariosCargados->get($hId);
            if ($horario) {
                $cursoId   = $horario->id_curso;
                $seccionId = $horario->id_seccion;

                if (isset($seccionesPorCurso[$cursoId]) && $seccionesPorCurso[$cursoId] !== $seccionId) {
                    throw new \Exception("No puede matricular la asignatura '{$horario->curso->nombre}' en dos secciones diferentes.");
                }

                $seccionesPorCurso[$cursoId] = $seccionId;
            }
        }
    }

    private function validarCruceHorarioEstudiante($horariosCargados): void
    {
        $lista = $horariosCargados->values();

        for ($i = 0; $i < $lista->count(); $i++) {
            for ($j = $i + 1; $j < $lista->count(); $j++) {
                $h1 = $lista[$i];
                $h2 = $lista[$j];

                if ($h1->id_curso !== $h2->id_curso && $h1->dia === $h2->dia) {
                    if ($h1->hora_inicio < $h2->hora_fin && $h1->hora_fin > $h2->hora_inicio) {
                        throw new \Exception("Cruce de horario el {$h1->dia}: '{$h1->curso->nombre}' ({$h1->hora_inicio}-{$h1->hora_fin}) se solapa con '{$h2->curso->nombre}' ({$h2->hora_inicio}-{$h2->hora_fin}).");
                    }
                }
            }
        }
    }

    private function validarLimiteCreditos($horariosCargados): void
    {
        $cursosIds = $horariosCargados->pluck('id_curso')->unique();
        $totalCreditos = (float) Curso::whereIn('id', $cursosIds)->sum('creditos');

        if ($totalCreditos > self::LIMITE_MAX_CREDITOS) {
            throw new \Exception("La carga académica seleccionada ({$totalCreditos} créditos) excede el límite máximo permitido de " . self::LIMITE_MAX_CREDITOS . " créditos por periodo lectivo.");
        }
    }

    private function validarAforoHorarios(array $cursosSeleccionados, $horariosCargados, ?Matricula $matricula = null): void
    {
        foreach ($cursosSeleccionados as $item) {
            $horario = $horariosCargados->get($item['horario_id']);

            if ($horario && $horario->capacidad > 0) {
                $matriculadosActuales = MatriculaCurso::where('horario_id', $horario->id)
                    ->when($matricula && $matricula->exists, fn ($q) => $q->where('matricula_id', '<>', $matricula->id))
                    ->count();

                if ($matriculadosActuales >= $horario->capacidad) {
                    $nombreCurso = $horario->curso?->nombre ?? 'Asignatura';
                    $seccionNom  = $horario->seccion?->nombre ?? 'Única';
                    throw new \Exception("No hay vacantes disponibles para '{$nombreCurso}' en la Sección {$seccionNom} (Aforo máximo: {$horario->capacidad}).");
                }
            }
        }
    }

    private function validarPeriodoActivo(int $periodoId): void
    {
        $periodo = Periodo::find($periodoId);
        if ($periodo && ! $periodo->activo) {
            throw new \Exception("No se pueden registrar matrículas en un periodo académico que se encuentra cerrado o inactivo.");
        }
    }
}