<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\DB;
class Agenda extends Model
{

    use HasFactory;
    protected $table = 'agendas';
    
    protected $fillable = [
        'estado',
        'duracionturno',
        'profesional_id',
    ];

    public function profesional() 
    {
        return $this->belongsTo(Profesional::class, 'profesional_id','id');
    }  

    public function diaDeAtencion() 
    {
        return $this->hasMany(DiaDeAtencion::class, 'agenda_id','id');
    }  

    public function turno()
    {
        return $this->hasMany(Turno::class, 'agenda_id','id');
    }

    public function feriado()
    {
        return $this->hasMany(Feriado::class, 'agenda_id','id');
    }

    public function modoVacaciones() 
    {
        return $this->hasMany(ModoVacaciones::class, 'agenda_id', 'id');
    }

    public function formularios()
{
    return $this->belongsToMany(
        \App\Models\Formulario::class,
        'profesionales_formularios',
        'profesional_id',
        'formulario_id',
        'profesional_id',
        'id'
    )->withPivot('estadoformulario')->withTimestamps();
}

    public function estaDisponible($fecha, $hora, $agendaId = null) // Verificar si la agenda está disponible para una fecha y hora específicas
    {
        $agenda = $agendaId ? self::find($agendaId) : $this;
        if (!$agenda) {
            return false;
        }

        // Verificar si la fecha es un feriado
        if ($agenda->feriado()->where('fecha', $fecha)->exists()) {
            return false;
        }

        // Verificar si la fecha está dentro de un período de vacaciones
        if ($agenda->modoVacaciones()->where('inicio', '<=', $fecha)->where('fin', '>=', $fecha)->exists()) {
            return false;
        }

        // Verificar si el día de atención corresponde al día de la semana de la fecha
        $diaSemana = date('N', strtotime($fecha)); // 1 (lunes) a 7 (domingo)
        $diaAtencion = $agenda->diaDeAtencion()->where('dia_id', $diaSemana)->first();
        if (!$diaAtencion) {
            return false;
        }

        // Verificar si la hora está dentro del horario de atención
        if (!$diaAtencion->horariosAtenciones()->where('horariocomienzo', '<=', $hora)->where('horariofin', '>=', $hora)->exists()) {
            return false;
        }

        // Verificar si la hora no está dentro de un horario de receso
        if ($diaAtencion->horariosRecesos()->where('comienzo', '<=', $hora)->where('fin', '>=', $hora)->exists()) {
            return false;
        }

        // Verificar si ya existe un turno para esa fecha y hora
        if ($agenda->turno()->where('inicio', $fecha . ' ' . $hora)->exists()) {
            return false;
        }

        return true;
    }

    public function obtenerTurnoDisponible($idProfesional, $tipopreferencia = 'INDISTINTO', $diasPreferencia = []) //Devuelve el turno disponible más cercano según preferencias
    {
        // Inicializa estructuras de salida con valores por defecto.
        $DiasDeAtenciones = array_fill(0, 7, array_fill(0, 5, null));
        $tipo = null;
        $recesos = [];
        $vacaciones = [];
        $feriados = [];
        $turnoMasCercano = null;

        // Busca la agenda asociada al profesional recibido por parámetro.
        $agendaId = self::where('profesional_id', $idProfesional)->value('id');

        // Si no existe agenda para ese profesional, devuelve la estructura vacía.
        if (!$agendaId) {
            return null;
        }

        // Obtiene días de atención y sus horarios AM/PM para la agenda encontrada.
        // [fila][0]=dia_id, [fila][1]=inicio AM, [fila][2]=fin AM, [fila][3]=inicio PM, [fila][4]=fin PM

        $filasAtencion = DB::table('diasdeatenciones as d')
            ->leftJoin('horariosatenciones as h', 'h.diadeatencion_id', '=', 'd.id')
            ->where('d.agenda_id', $agendaId)
            ->select('d.dia_id', 'h.horariocomienzo', 'h.horariofin', 'h.tipo')
            ->orderBy('d.dia_id')
            ->get();

        // Recorre cada fila y la ubica en la matriz de 7x5 según día y tipo de horario.
        foreach ($filasAtencion as $filaAtencion) {
            $fila = (int) $filaAtencion->dia_id - 1;

            // Ignora valores fuera del rango esperado de días (1 a 7).
            if ($fila < 0 || $fila > 6) {
                continue;
            }

            // Guarda el identificador del día en la primera columna.
            $DiasDeAtenciones[$fila][0] = $filaAtencion->dia_id;

            // Si no hay tipo de horario, salta al siguiente registro.
            if ($filaAtencion->tipo === null) {
                continue;
            }

            // Normaliza y guarda el tipo actual (AM o PM).
            $tipoActual = strtoupper(trim((string) $filaAtencion->tipo));
            $tipo = $tipoActual;

            // Completa columnas de mañana (inicio y fin).
            if ($tipoActual === 'AM') {
                $DiasDeAtenciones[$fila][1] = $filaAtencion->horariocomienzo;
                $DiasDeAtenciones[$fila][2] = $filaAtencion->horariofin;
            }

            // Completa columnas de tarde (inicio y fin).
            if ($tipoActual === 'PM') {
                $DiasDeAtenciones[$fila][3] = $filaAtencion->horariocomienzo;
                $DiasDeAtenciones[$fila][4] = $filaAtencion->horariofin;
            }
        }

        // Trae los recesos de la agenda y los transforma a una matriz [dia_id, comienzo, fin].
        $recesos = DB::table('horariosrecesos as r')
            ->join('diasdeatenciones as d', 'd.id', '=', 'r.diadeatencion_id')
            ->where('d.agenda_id', $agendaId)
            ->select('d.dia_id', 'r.comienzo', 'r.fin')
            ->orderBy('d.dia_id')
            ->get()
            ->map(function ($receso) {
                return [
                    $receso->dia_id,
                    $receso->comienzo,
                    $receso->fin,
                ];
            })
            ->values()
            ->all();

        // Trae períodos de vacaciones y los transforma a una matriz [inicio, fin].
        $vacaciones = DB::table('modosvacaciones')
            ->where('agenda_id', $agendaId)
            ->select('inicio', 'fin')
            ->orderBy('inicio')
            ->get()
            ->map(function ($vacacion) {
                return [
                    $vacacion->inicio,
                    $vacacion->fin,
                ];
            })
            ->values()
            ->all();

        // Trae todos los feriados de la agenda y los transforma en una matriz de fechas.
        $feriados = DB::table('feriados')
            ->where('agenda_id', $agendaId)
            ->select('fecha')
            ->orderBy('fecha')
            ->pluck('fecha')
            ->values()
            ->all();

        // Normaliza preferencia horaria (AM, PM o INDISTINTO).
        $tipopreferencia = strtoupper(trim((string) $tipopreferencia));
        if (!in_array($tipopreferencia, ['AM', 'PM', 'INDISTINTO'], true)) {
            $tipopreferencia = 'INDISTINTO';
        }

        // Convierte los días preferidos en texto a ids de 1 (lunes) a 7 (domingo).
        $mapaDias = [
            'lunes' => 1,
            'martes' => 2,
            'miercoles' => 3,
            'jueves' => 4,
            'viernes' => 5,
            'sabado' => 6,
            'domingo' => 7,
        ];
        $diasPreferidosIds = [];
        foreach ((array) $diasPreferencia as $diaPreferido) {
            $diaNormalizado = strtolower(trim((string) $diaPreferido));
            $diaNormalizado = strtr($diaNormalizado, [
                'á' => 'a',
                'é' => 'e',
                'í' => 'i',
                'ó' => 'o',
                'ú' => 'u',
            ]);

            if (isset($mapaDias[$diaNormalizado])) {
                $diasPreferidosIds[] = $mapaDias[$diaNormalizado];
            }
        }
        $diasPreferidosIds = array_values(array_unique($diasPreferidosIds));

        // Toma la duración de turno de la agenda; si no existe, usa 30 minutos.
        $duracionTurno = (int) (self::where('id', $agendaId)->value('duracionturno') ?? 30);
        if ($duracionTurno <= 0) {
            $duracionTurno = 30;
        }

        // Define el punto de inicio de la búsqueda desde el día siguiente (para evitar asignar turnos inmediatos del día actual).
        $baseTimestamp = strtotime('tomorrow');

        // Crea índices rápidos para validar fechas bloqueadas y horarios ocupados.
        $feriadosLookup = array_flip($feriados);
        $recesosPorDia = [];
        foreach ($recesos as $receso) {
            $diaId = (int) $receso[0];
            if (!isset($recesosPorDia[$diaId])) {
                $recesosPorDia[$diaId] = [];
            }
            $recesosPorDia[$diaId][] = [$receso[1], $receso[2]];
        }

        $turnosOcupadosLookup = [];
        $turnosOcupados = DB::table('turnos')
            ->where('agenda_id', $agendaId)
            ->where('inicio', '>=', date('Y-m-d', $baseTimestamp))
            ->select('inicio')
            ->get();

        foreach ($turnosOcupados as $turnoOcupado) {
            $tsTurno = strtotime((string) $turnoOcupado->inicio);
            if ($tsTurno === false) {
                continue;
            }

            $turnosOcupadosLookup[date('Y-m-d H:i:s', $tsTurno)] = true;
        }

        // Busca el primer turno disponible respetando tipo, días preferidos y reglas de agenda.
        for ($offsetDias = 0; $offsetDias <= 120; $offsetDias++) {
            $tsDia = strtotime(date('Y-m-d', $baseTimestamp) . ' +' . $offsetDias . ' day');
            if ($tsDia === false) {
                continue;
            }

            $fechaIterada = date('Y-m-d', $tsDia);
            $diaId = (int) date('N', $tsDia);

            if (!empty($diasPreferidosIds) && !in_array($diaId, $diasPreferidosIds, true)) {
                continue;
            }

            if (isset($feriadosLookup[$fechaIterada])) {
                continue;
            }

            $enVacaciones = false;
            foreach ($vacaciones as $vacacion) {
                if ($fechaIterada >= $vacacion[0] && $fechaIterada <= $vacacion[1]) {
                    $enVacaciones = true;
                    break;
                }
            }
            if ($enVacaciones) {
                continue;
            }

            $filaDia = $DiasDeAtenciones[$diaId - 1] ?? null;
            if (!$filaDia || $filaDia[0] === null) {
                continue;
            }

            $ventanas = [];
            if (($tipopreferencia === 'AM' || $tipopreferencia === 'INDISTINTO') && $filaDia[1] !== null && $filaDia[2] !== null) {
                $ventanas[] = ['tipo' => 'AM', 'inicio' => $filaDia[1], 'fin' => $filaDia[2]];
            }
            if (($tipopreferencia === 'PM' || $tipopreferencia === 'INDISTINTO') && $filaDia[3] !== null && $filaDia[4] !== null) {
                $ventanas[] = ['tipo' => 'PM', 'inicio' => $filaDia[3], 'fin' => $filaDia[4]];
            }

            foreach ($ventanas as $ventana) {
                $inicioVentanaTs = strtotime($fechaIterada . ' ' . $ventana['inicio']);
                $finVentanaTs = strtotime($fechaIterada . ' ' . $ventana['fin']);
                if ($inicioVentanaTs === false || $finVentanaTs === false || $inicioVentanaTs >= $finVentanaTs) {
                    continue;
                }

                for ($slotTs = $inicioVentanaTs; ($slotTs + ($duracionTurno * 60)) <= $finVentanaTs; $slotTs += ($duracionTurno * 60)) {
                    $slotFecha = date('Y-m-d', $slotTs);
                    $slotHora = date('H:i:s', $slotTs);
                    $slotDateTime = $slotFecha . ' ' . $slotHora;
                    $slotFinTs = $slotTs + ($duracionTurno * 60);

                    if (isset($turnosOcupadosLookup[$slotDateTime])) {
                        continue;
                    }

                    $solapaReceso = false;
                    foreach ($recesosPorDia[$diaId] ?? [] as $recesoDia) {
                        $recesoInicioTs = strtotime($slotFecha . ' ' . $recesoDia[0]);
                        $recesoFinTs = strtotime($slotFecha . ' ' . $recesoDia[1]);
                        if ($recesoInicioTs === false || $recesoFinTs === false) {
                            continue;
                        }

                        if ($slotTs < $recesoFinTs && $slotFinTs > $recesoInicioTs) {
                            $solapaReceso = true;
                            break;
                        }
                    }

                    if ($solapaReceso) {
                        continue;
                    }

                    if (!$this->estaDisponible($slotFecha, $slotHora, $agendaId)) {
                        continue;
                    }

                    $turnoMasCercano = [
                        'fecha' => $slotFecha,
                        'hora' => $slotHora,
                        'fechaHora' => $slotDateTime,
                        'tipo' => $ventana['tipo'],
                        'duracionMinutos' => $duracionTurno,
                    ];
                    break 3;
                }
            }
        }

        // Devuelve solo la fecha/hora del turno más cercano o null si no hay disponibilidad.
        return $turnoMasCercano['fechaHora'] ?? null;
    }

    public function crearDiaDeAtencion($diaId, $horarioComienzo, $horarioFin, $tipo)
    {
        $tipoNormalizado = strtoupper(trim((string) $tipo));
        if (!in_array($tipoNormalizado, ['AM', 'PM'], true)) {
            throw new \InvalidArgumentException('El tipo debe ser AM o PM.');
        }

        $horarioComienzoNormalizado = $this->normalizarHora($horarioComienzo, 'horarioComienzo');
        $horarioFinNormalizado = $this->normalizarHora($horarioFin, 'horarioFin');

        if (strtotime('1970-01-01 ' . $horarioComienzoNormalizado) >= strtotime('1970-01-01 ' . $horarioFinNormalizado)) {
            throw new \InvalidArgumentException('horarioComienzo debe ser menor que horarioFin.');
        }

        return DB::transaction(function () use ($diaId, $tipoNormalizado, $horarioComienzoNormalizado, $horarioFinNormalizado) {
            $diaAtencion = DiaDeAtencion::firstOrCreate(
                ['agenda_id' => $this->id, 'dia_id' => $diaId],
                ['descripcion' => 'Dia de atencion']
            );

            HorarioDeAtencion::updateOrCreate(
                [
                    'diadeatencion_id' => $diaAtencion->id,
                    'tipo' => $tipoNormalizado,
                ],
                [
                    'horariocomienzo' => $horarioComienzoNormalizado,
                    'horariofin' => $horarioFinNormalizado,
                ]
            );

            return $diaAtencion;
        });
    }

    public function eliminarDiaDeAtencion($diaId)
    {
        return DB::transaction(function () use ($diaId) {
            $diaAtencion = DiaDeAtencion::where('agenda_id', $this->id)->where('dia_id', $diaId)->first();
            if ($diaAtencion) {
                HorarioDeAtencion::where('diadeatencion_id', $diaAtencion->id)->delete();
                HorarioReceso::where('diadeatencion_id', $diaAtencion->id)->delete();
                $diaAtencion->delete();
            }
        });
    }

    public function crearModoVacaciones($inicio, $fin, $descripcion = null)
    {
        return ModoVacaciones::create([
            'agenda_id' => $this->id,
            'inicio' => $inicio,
            'fin' => $fin,
            'descripcion' => $descripcion,
        ]);
        
    }

    public function eliminarModoVacaciones($id)
    {
        $modoVacaciones = ModoVacaciones::where('agenda_id', $this->id)->where('id', $id)->first();
        if ($modoVacaciones) {
            $modoVacaciones->delete();
        }
    }

    public function crearFeriado($fecha, $descripcion = null)
    {
        return Feriado::create([
            'agenda_id' => $this->id,
            'fecha' => $fecha,
            'descripcion' => $descripcion,
        ]);
    }

    public function eliminarFeriado($id)
    {
        $feriado = Feriado::where('agenda_id', $this->id)->where('id', $id)->first();
        if ($feriado) {
            $feriado->delete();
        }
    }

    public function guardarTurno($inicio, $correo = null, $nombrecompleto = null, $descripcion = null, $clienteId = null, $estadoturnoId = null, $profesionalId = null, $servicioId = null, $modalidadId = null, $agendaId = null)
    {
        $inicioNormalizado = $this->normalizarDatetime($inicio, 'inicio');

        $turno = Turno::create([
            'agenda_id' => $agendaId ?? $this->id,
            'correo' => $correo,
            'nombrecompleto' => $nombrecompleto,
            'descripcion' => $descripcion,
            'cliente_id' => $clienteId,
            'estadoturno_id' => $estadoturnoId,
            'profesional_id' => $profesionalId,
            'servicio_id' => $servicioId,
            'modalidad_id' => $modalidadId,
            'inicio' => $inicioNormalizado,
        ]);

        return $turno;
    }

    private function normalizarHora($valor, $campo)
    {
        if ($valor instanceof \DateTimeInterface) {
            return $valor->format('H:i:s');
        }

        $timestamp = strtotime(trim((string) $valor));
        if ($timestamp === false) {
            throw new \InvalidArgumentException('El campo ' . $campo . ' debe ser una hora valida.');
        }

        return date('H:i:s', $timestamp);
    }

    private function normalizarDatetime($valor, $campo)
    {
        if ($valor instanceof \DateTimeInterface) {
            return $valor->format('Y-m-d H:i:s');
        }

        $timestamp = strtotime(trim((string) $valor));
        if ($timestamp === false) {
            throw new \InvalidArgumentException('El campo ' . $campo . ' debe ser una fecha y hora valida.');
        }

        return date('Y-m-d H:i:s', $timestamp);
    }

    public function confirmarTurno($id)
    {
        $turno = $this->turno()->find($id);
        if (!$turno) {
            return null;
        }

        return $turno->confirmar();
    }

    public function cancelarTurno($id)
    {
        $turno = $this->turno()->find($id);
        if (!$turno) {
            return null;
        }

        return $turno->cancelar();
    }

    public function reprogramarTurno($id)
    {
        $turno = $this->turno()->find($id);
        if (!$turno) {
            return null;
        }

        return $turno->reprogramar();
    }

    public function marcarClienteAusente($id)
    {
        $turno = $this->turno()->find($id);
        if (!$turno) {
            return null;
        }

        return $turno->clienteAusente();
    }

    public function marcarClientePresente($id)
    {
        $turno = $this->turno()->find($id);
        if (!$turno) {
            return null;
        }

        return $turno->clientePresente();
    }

}
