<?php

namespace App\Observers;

use App\Enums\BookingStatus;
use App\Models\Booking;
use App\Models\BookingStatusHistory;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

/**
 * Observer del modelo Booking. Responsabilidades:
 *  - Generar `code` único legible (BK-YYYY-NNNNNN) al crear.
 *  - Normalizar `scheduled_at` (booking_date + start_time) si no vino seteado.
 *  - Registrar cada cambio de estado en booking_status_history.
 *  - Tocar las estadísticas del cliente cuando se completa.
 */
class BookingObserver
{
    /** @var array<int, array{from: ?string, to: ?string}> Stash temporal entre updating/updated. */
    protected static array $statusChanges = [];

    public function creating(Booking $booking): void
    {
        if (empty($booking->code)) {
            $booking->code = $this->generateCode($booking);
        }

        if (empty($booking->scheduled_at) && $booking->booking_date && $booking->start_time) {
            $booking->scheduled_at = $booking->booking_date->copy()
                ->setTimeFromTimeString((string) $booking->start_time);
        }

        if (empty($booking->end_time) && $booking->start_time && $booking->duration_minutes) {
            $booking->end_time = $booking->scheduled_at
                ?->copy()
                ->addMinutes($booking->duration_minutes)
                ->format('H:i:s');
        }

        if (empty($booking->total_amount)) {
            $booking->total_amount = max(0, ($booking->service_price ?? 0) - ($booking->discount_amount ?? 0));
        }
    }

    public function created(Booking $booking): void
    {
        BookingStatusHistory::create([
            'company_id'      => $booking->company_id,
            'booking_id'      => $booking->id,
            'from_status'     => null,
            'to_status'       => $booking->status?->value,
            'changed_by'      => auth()->id(),
            'changed_by_role' => auth()->user()?->roles->first()?->name,
            'reason'          => 'Reserva creada',
            'created_at'      => now(),
        ]);
    }

    public function updating(Booking $booking): void
    {
        if (! $booking->isDirty('status')) {
            return;
        }

        $original = $booking->getOriginal('status');
        $current  = $booking->status;

        // Normalizamos a string para comparar y guardar.
        $from = $original instanceof BookingStatus ? $original->value : $original;
        $to   = $current instanceof BookingStatus ? $current->value : $current;

        // Marcadores temporales según destino.
        if ($current === BookingStatus::Confirmed && empty($booking->confirmed_at)) {
            $booking->confirmed_at = now();
        }
        if ($current === BookingStatus::Completed && empty($booking->completed_at)) {
            $booking->completed_at = now();
        }
        if ($current === BookingStatus::Cancelled && empty($booking->cancelled_at)) {
            $booking->cancelled_at = now();
            if (auth()->id() && empty($booking->cancelled_by)) {
                $booking->cancelled_by = auth()->id();
            }
        }

        // Stash para registrar tras update (usamos id si existe, fallback al objeto-hash).
        static::$statusChanges[$booking->id ?? spl_object_id($booking)] = [
            'from' => $from,
            'to'   => $to,
        ];
    }

    public function updated(Booking $booking): void
    {
        $key = $booking->id ?? spl_object_id($booking);
        $change = static::$statusChanges[$key] ?? null;
        if (! $change) {
            return;
        }
        unset(static::$statusChanges[$key]);

        BookingStatusHistory::create([
            'company_id'      => $booking->company_id,
            'booking_id'      => $booking->id,
            'from_status'     => $change['from'],
            'to_status'       => $change['to'],
            'changed_by'      => auth()->id(),
            'changed_by_role' => auth()->user()?->roles->first()?->name,
            'created_at'      => now(),
        ]);

        // Estadísticas del cliente al completar.
        if ($change['to'] === BookingStatus::Completed->value && $booking->customer) {
            $customer = $booking->customer;
            $customer->forceFill([
                'last_booking_at'  => now(),
                'first_booking_at' => $customer->first_booking_at ?? now(),
            ])->save();
            $customer->increment('total_bookings');
            $customer->increment('total_spent', (float) $booking->total_amount);
        }
    }

    /**
     * Genera un código único secuencial mensual del tipo BK-202604-000123.
     * Bloquea con un lock corto en cache para evitar colisiones bajo concurrencia.
     */
    protected function generateCode(Booking $booking): string
    {
        $period = now()->format('Ym');

        // Buscamos el último número del periodo
        $last = Booking::withoutGlobalScopes()
            ->where('company_id', $booking->company_id)
            ->where('code', 'like', "BK-{$period}-%")
            ->orderByDesc('id')
            ->value('code');

        $next = $last
            ? ((int) Str::afterLast($last, '-')) + 1
            : 1;

        return sprintf('BK-%s-%06d', $period, $next);
    }
}
