<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class OrdenProduccion extends Model
{
    use HasFactory;

    protected $fillable = [
        'numero_orden',
        'prenda_modelo',
        'referencia',
        'cantidad_total',
        'tela_id',
        'producto_id',
        'fecha_inicio',
        'fecha_entrega_estimada',
        'estado',
        'metros_requeridos',
        'tela_reservada',
        'imagenes',
    ];

    /**
     * Casts
     *
     * @var array
     */
    protected $casts = [
        'imagenes' => 'array',
    ];

    public function tela()
    {
        return $this->belongsTo(Tela::class);
    }

    public function traslados()
    {
        return $this->hasMany(\App\Models\TrasladoPrenda::class);
    }

    public function confeccions()
    {
        return $this->hasMany(\App\Models\Confeccion::class);
    }

    public function confecciones()
    {
        return $this->confeccions();
    }

    public function tintorerias()
    {
        return $this->hasMany(\App\Models\Tintoreria::class);
    }

    public function prensillas()
    {
        return $this->hasMany(\App\Models\Prensilla::class);
    }

    public function ojales()
    {
        return $this->hasMany(\App\Models\Ojal::class);
    }

    public function procesosAcabado()
    {
        return $this->hasMany(\App\Models\ProcesoAcabado::class);
    }

    public function inventarioPrenda()
    {
        return $this->hasMany(\App\Models\InventarioPrenda::class);
    }

    public function producto()
    {
        return $this->belongsTo(\App\Models\Producto::class, 'producto_id');
    }



    protected static function booted()
    {
        static::creating(function ($orden) {
            if (!$orden->numero_orden) {
                $ultimoId = self::max('id') + 1;

                $orden->numero_orden = 'OP-' . str_pad($ultimoId, 6, '0', STR_PAD_LEFT);
            }

            // Fecha de inicio por defecto hoy
            if (! isset($orden->fecha_inicio) || ! $orden->fecha_inicio) {
                $orden->fecha_inicio = now()->toDateString();
            }

            // Asegurar que 'referencia' no sea NULL para evitar violaciones en bases que aún no aplicaron la migración
            if (! isset($orden->referencia) || $orden->referencia === null) {
                $orden->referencia = '';
            }

            // Reserva de tela al crear cuando la OP usa una tela: restar metros_disponibles inmediatamente
            if ($orden->tela_id && ($orden->metros_requeridos ?? 0) > 0) {
                $tela = \App\Models\Tela::find($orden->tela_id);
                if (! $tela) {
                    throw \Illuminate\Validation\ValidationException::withMessages([
                        'tela_id' => 'No se encontró la tela asociada.',
                    ]);
                }

                if ($tela->metros_disponibles < $orden->metros_requeridos) {
                    throw \Illuminate\Validation\ValidationException::withMessages([
                        'metros_requeridos' => 'No hay suficientes metros disponibles para reservar (' . $tela->metros_disponibles . ').',
                    ]);
                }

                // Reservar: restar metros y marcar orden
                $tela->metros_disponibles = $tela->metros_disponibles - $orden->metros_requeridos;
                $tela->save();

                $orden->tela_reservada = true;
            }
        });

        // Validar y reservar tela al cambiar de estado a 'en_corte'
        static::updating(function ($orden) {
            // Validación: si se especifica tela y metros_requeridos, verificar disponibilidad
            if ($orden->tela_id && $orden->metros_requeridos !== null) {
                $tela = \App\Models\Tela::find($orden->tela_id);
                $originalMetros = $orden->getOriginal('metros_requeridos') ?? 0;
                $reservedAlready = $orden->tela_reservada ? $originalMetros : 0;
                $available = $tela ? ($tela->metros_disponibles + $reservedAlready) : null;

                if ($available !== null && $orden->metros_requeridos > $available) {
                    throw \Illuminate\Validation\ValidationException::withMessages([
                        'metros_requeridos' => 'No hay suficientes metros de tela disponibles. Disponibles: ' . ($available),
                    ]);
                }
            }

            // Ajustes automáticos de reserva cuando cambia la tela o los metros requeridos
            $originalTelaId = $orden->getOriginal('tela_id');
            $originalMetros = $orden->getOriginal('metros_requeridos') ?? 0;
            $newTelaId = $orden->tela_id;
            $newMetros = $orden->metros_requeridos ?? 0;

            // Si cambió la tela en la OP, devolver los metros reservados en la tela anterior (si aplica)
            if ($originalTelaId && $originalTelaId != $newTelaId && $originalMetros > 0) {
                $oldTela = \App\Models\Tela::find($originalTelaId);
                if ($oldTela) {
                    $oldTela->metros_disponibles = $oldTela->metros_disponibles + $originalMetros;
                    $oldTela->save();
                }
            }

            // Reservar metros en la nueva tela si se cambió
            if ($newTelaId && $originalTelaId != $newTelaId && $newMetros > 0) {
                $newTela = \App\Models\Tela::find($newTelaId);
                if (! $newTela) {
                    throw \Illuminate\Validation\ValidationException::withMessages([
                        'tela_id' => 'No se encontró la tela asociada.',
                    ]);
                }

                if ($newTela->metros_disponibles < $newMetros) {
                    throw \Illuminate\Validation\ValidationException::withMessages([
                        'metros_requeridos' => 'No hay suficientes metros disponibles para reservar (' . $newTela->metros_disponibles . ').',
                    ]);
                }

                $newTela->metros_disponibles = $newTela->metros_disponibles - $newMetros;
                $newTela->save();
                $orden->tela_reservada = true;
            }

            // Si la misma tela y cambiaron los metros, ajustar la diferencia
            if ($originalTelaId == $newTelaId && $originalMetros != $newMetros && $newTelaId) {
                $delta = $newMetros - $originalMetros;
                $tela = \App\Models\Tela::find($newTelaId);
                if ($tela) {
                    if ($delta > 0) {
                        if ($tela->metros_disponibles < $delta) {
                            throw \Illuminate\Validation\ValidationException::withMessages([
                                'metros_requeridos' => 'No hay suficientes metros disponibles para aumentar la reserva.',
                            ]);
                        }
                        $tela->metros_disponibles = $tela->metros_disponibles - $delta;
                    } elseif ($delta < 0) {
                        $tela->metros_disponibles = $tela->metros_disponibles + abs($delta);
                    }
                    $tela->save();
                }
            }

            $originalEstado = $orden->getOriginal('estado');
            $nuevoEstado = $orden->estado;

            // Reservar cuando pasamos a en_corte y no estaba reservado
            if ($originalEstado !== 'en_corte' && $nuevoEstado === 'en_corte' && ! $orden->tela_reservada) {
                if ($orden->tela_id && $orden->metros_requeridos > 0) {
                    $tela = \App\Models\Tela::find($orden->tela_id);
                    if (! $tela) {
                        throw \Illuminate\Validation\ValidationException::withMessages([
                            'tela_id' => 'No se encontró la tela asociada.',
                        ]);
                    }

                    if ($tela->metros_disponibles < $orden->metros_requeridos) {
                        throw \Illuminate\Validation\ValidationException::withMessages([
                            'metros_requeridos' => 'No hay suficientes metros disponibles para reservar (' . $tela->metros_disponibles . ').',
                        ]);
                    }

                    // Reservar: restar metros y marcar orden
                    $tela->metros_disponibles = $tela->metros_disponibles - $orden->metros_requeridos;
                    $tela->save();

                    $orden->tela_reservada = true;
                }
            }

            // Si retrocede desde en_corte y ya estaba reservado, devolver la tela
            if ($originalEstado === 'en_corte' && $nuevoEstado !== 'en_corte' && $orden->tela_reservada) {
                if ($orden->tela_id && $orden->metros_requeridos > 0) {
                    $tela = \App\Models\Tela::find($orden->tela_id);
                    if ($tela) {
                        $tela->metros_disponibles = $tela->metros_disponibles + $orden->metros_requeridos;
                        $tela->save();
                    }
                }

                $orden->tela_reservada = false;
            }
        });    }

    // Cantidad completada (desde procesos de acabados recibidos)
    public function getRealizadoAttribute()
    {
        return (int) $this->procesosAcabado()->whereNotNull('fecha_recepcion')->sum('cantidad_recibida');
    }

    // Faltante para completar la OP
    public function getFaltantesAttribute()
    {
        return max(0, (int)$this->cantidad_total - $this->realizado);
    }

    public function canBeFinalized(): bool
    {
        return $this->realizado >= (int)$this->cantidad_total;
    }

    public function tryFinalize(): bool
    {
        if ($this->canBeFinalized()) {
            $this->update(['estado' => 'finalizada']);

            // Crear inventario si corresponde
            $this->crearInventarioPorFinalizacion();

            return true;
        }
        return false;
    }

    /**
     * Estado calculado para mostrar en UI: si está completada o la última etapa que tiene tareas pendientes.
     */
    public function getEstadoDisplayAttribute()
    {
        if ($this->canBeFinalized() || $this->estado === 'finalizada') {
            return 'finalizada';
        }

        // Revisar etapas de último a primero para encontrar la etapa que aún tiene tareas pendientes
        if (\App\Models\ProcesoAcabado::where('orden_produccion_id', $this->id)->whereNull('fecha_recepcion')->exists()) {
            return 'en_acabados';
        }

        if (\App\Models\Tintoreria::where('orden_produccion_id', $this->id)->whereNull('fecha_recepcion')->exists()) {
            return 'en_tintoreria';
        }

        if (\App\Models\Confeccion::where('orden_produccion_id', $this->id)->whereNull('fecha_recepcion')->exists()) {
            return 'en_confeccion';
        }

        // Por defecto devolver el estado actual (incluye en_corte u otros)
        return $this->estado ?? 'en_corte';
    }

    /**
     * Forzar finalización manualmente (útil si desea marcar como finalizada aunque no esté totalmente recibida)
     */
    public function manualFinalize(): void
    {
        $this->update(['estado' => 'finalizada']);

        // Intentar crear inventario con lo recibido hasta ahora
        $this->crearInventarioPorFinalizacion();
    }

    public function avanzarEstado(): void
    {
        $flujo = [
            'en_corte',
            'en_confeccion',
            'en_tintoreria',
            'en_acabados',
            'finalizada',
        ];

        $actual = array_search($this->estado, $flujo, true);

        if ($actual !== false && isset($flujo[$actual + 1])) {
            $next = $flujo[$actual + 1];

            // Si el siguiente estado es finalizada, verificar que esté completa
            if ($next === 'finalizada' && ! $this->canBeFinalized()) {
                return; // No avanzar a finalizada si no está completada
            }

            $this->update([
                'estado' => $next,
            ]);

            // Si la orden llega a finalizada, crear entrada en inventario con lo que haya ingresado
            if ($this->estado === 'finalizada') {
                $this->crearInventarioPorFinalizacion();
            }
        }
    }

    protected function crearInventarioPorFinalizacion(): void
    {
        // Sumar procesos de acabados recibidos para determinar cantidad terminada
        $cantidad = \App\Models\ProcesoAcabado::where('orden_produccion_id', $this->id)
            ->whereNotNull('fecha_recepcion')
            ->sum('cantidad_recibida');

        // Crear inventario solo si hay traslados a bodega con cantidad
        if ($cantidad > 0 && !\App\Models\InventarioPrenda::where('orden_produccion_id', $this->id)->exists()) {
            // Determinar producto asociado: preferir producto_id en la OP, sino crear/buscar por prenda_modelo
            $productoId = $this->producto_id;

            if (! $productoId) {
                $nombre = $this->prenda_modelo ?? $this->referencia ?? null;
                if ($nombre) {
                    // Generar código de barras único por defecto para productos creados automáticamente
                    $codigo = 'AUTOP-' . $this->id . '-' . time();
                    $codigo = substr($codigo, 0, 50);

                    // Asegurar unicidad
                    while (\App\Models\Producto::where('codigo_barras', $codigo)->exists()) {
                        $codigo .= '-' . rand(0, 9);
                        $codigo = substr($codigo, 0, 50);
                    }

                    $producto = \App\Models\Producto::firstOrCreate(
                        ['nombre' => $nombre],
                        [
                            'descripcion' => 'Creado automáticamente desde OrdenProduccion #' . $this->id,
                            'stock' => 0,
                            'estado' => true,
                            'precio_compra' => 0,
                            'precio_venta' => 0,
                            'unidad_medida' => 'unidad',
                            'codigo_barras' => $codigo,
                        ]
                    );

                    $productoId = $producto->id;

                    // Guardar vínculo en la OP para futuras referencias
                    $this->producto_id = $productoId;
                    $this->save();
                }
            }

            \App\Models\InventarioPrenda::create([
                'orden_produccion_id' => $this->id,
                'cantidad_terminada' => $cantidad,
                'cantidad_disponible' => $cantidad,
                'fecha_ingreso' => now(),
                'estado' => 'en_bodega',
                'producto_id' => $productoId ?? null,
            ]);

            // Si se creó inventario, también actualizar stock del producto asociado (handler en InventarioPrenda lo hará)
        }
    }
}
