<?php

namespace App\Http\Livewire\Public;

use App\Models\Booking;
use App\Models\Company;
use App\Models\Coupon;
use App\Models\Employee;
use App\Models\Promotion;
use App\Models\Service;
use App\Services\Booking\AvailabilityService;
use App\Services\Booking\BookingService;
use App\Support\Tenant;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Livewire\Attributes\Computed;
use Livewire\Attributes\On;
use Livewire\Component;

/**
 * Wizard público de reserva. 4 pasos:
 *   1. Servicio
 *   2. Profesional (opcional: "cualquiera")
 *   3. Fecha + hora
 *   4. Datos del cliente + confirmación
 *
 * Funciona con cliente invitado (sin cuenta).
 */
class BookingWizard extends Component
{
    public Company $company;

    public int $step = 1;

    /* Paso 1 */
    public ?int $serviceId = null;

    /* Paso 2 */
    public ?int $employeeId = null;
    public bool $anyEmployee = false;

    /* Paso 3 */
    public string $date = '';     // YYYY-MM-DD
    public string $time = '';     // HH:mm
    public ?int $resolvedEmployeeId = null;  // empleado realmente asignado

    /* Paso 4 */
    public string $customer_first_name = '';
    public string $customer_last_name = '';
    public string $customer_phone = '';
    public string $customer_email = '';
    public string $customer_notes = '';
    public string $countryCallingCode = '+57';
    public string $coupon_code = '';
    public ?int $appliedCouponId = null;
    public ?string $couponMessage = null;
    public ?string $couponError = null;

    /* Resultado */
    public ?string $confirmedCode = null;
    public ?string $confirmedCancelUrl = null;
    public ?string $error = null;

    public function mount(string $slug): void
    {
        $this->company = Company::where('slug', $slug)->where('status', '!=', 'cancelled')->firstOrFail();
        Tenant::set($this->company);
        $this->date = now($this->company->timezone)->toDateString();
        $this->countryCallingCode = $this->inferCountryCallingCode();
    }

    public function rendering(): void
    {
        // Asegura que el global scope ve el tenant también en cada render.
        Tenant::set($this->company);
    }

    /* ----------------------- Computeds (datos del wizard) ----------------------- */

    #[Computed]
    public function services(): Collection
    {
        return $this->company->services()
            ->public()
            ->whereHas('employees', fn ($query) => $query->bookable())
            ->with('category:id,name')
            ->orderBy('name')
            ->get();
    }

    #[Computed]
    public function selectedService(): ?Service
    {
        return $this->serviceId ? Service::find($this->serviceId) : null;
    }

    #[Computed]
    public function selectedPromotion(): ?Promotion
    {
        if (! $this->serviceId) {
            return null;
        }

        return $this->featuredPromotions
            ->first(fn (Promotion $promotion) => $promotion->appliesToService($this->serviceId));
    }

    #[Computed]
    public function featuredPromotions(): Collection
    {
        return $this->company->promotions()
            ->active()
            ->where('is_featured', true)
            ->latest()
            ->get();
    }

    #[Computed]
    public function appliedCoupon(): ?Coupon
    {
        return $this->appliedCouponId ? Coupon::find($this->appliedCouponId) : null;
    }

    #[Computed]
    public function pricing(): array
    {
        $subtotal = (float) ($this->selectedService?->effectivePrice() ?? 0);
        $promotionDiscount = $this->selectedPromotion?->calculateDiscount($subtotal) ?? 0.0;
        $afterPromotion = max(0, $subtotal - $promotionDiscount);
        $couponDiscount = $this->appliedCoupon?->calculateDiscount($afterPromotion) ?? 0.0;

        return [
            'subtotal' => $subtotal,
            'promotion_discount' => min($subtotal, $promotionDiscount),
            'coupon_discount' => min($afterPromotion, $couponDiscount),
            'total' => max(0, $afterPromotion - $couponDiscount),
        ];
    }

    #[Computed]
    public function employees(): Collection
    {
        if (! $this->serviceId) return collect();
        return Service::find($this->serviceId)
            ?->employees()
            ->bookable()
            ->orderBy('first_name')
            ->get() ?? collect();
    }

    #[Computed]
    public function availableSlots(): Collection
    {
        if (! $this->serviceId || ! $this->date) {
            return collect();
        }

        return app(AvailabilityService::class)->slotsFor(
            $this->selectedService,
            $this->anyEmployee ? null : $this->employeeId,
            Carbon::parse($this->date, $this->company->timezone),
        );
    }

    #[Computed]
    public function dateOptions(): array
    {
        $tz = $this->company->timezone;
        $start = now($tz)->startOfDay();
        $end   = $start->copy()->addDays(min($this->company->booking_max_advance_days ?? 60, 30));
        $availability = app(AvailabilityService::class)->availableDates(
            $this->selectedService,
            $this->anyEmployee ? null : $this->employeeId,
            $start,
            $end,
        );

        $options = [];
        foreach ($availability as $d => $hasSlots) {
            $carbon = Carbon::parse($d, $tz);
            $options[] = [
                'date'    => $d,
                'day'     => $carbon->isoFormat('ddd'),
                'num'     => $carbon->format('d'),
                'month'   => $carbon->isoFormat('MMM'),
                'has'     => $hasSlots,
            ];
        }
        return $options;
    }

    /* ----------------------- Navegación de pasos ----------------------- */

    public function selectService(int $id): void
    {
        $this->serviceId = $id;
        $this->step = 2;
        $this->employeeId = null;
        $this->resolvedEmployeeId = null;
        $this->anyEmployee = false;
        $this->time = '';
        $this->clearCoupon();
    }

    #[On('public-service-selected')]
    public function selectServiceFromPublic(int $id): void
    {
        if (! $this->services->contains('id', $id)) {
            return;
        }

        $this->selectService($id);
    }

    public function selectEmployee(?int $id, bool $any = false): void
    {
        $this->employeeId  = $id;
        $this->anyEmployee = $any;
        $this->resolvedEmployeeId = null;
        $this->step = 3;
        $this->time = '';
    }

    public function selectDate(string $date): void
    {
        $this->date = $date;
        $this->time = '';
    }

    public function selectTime(string $time, int $employeeId): void
    {
        $this->time = $time;
        $this->resolvedEmployeeId = $employeeId;
        $this->step = 4;
    }

    public function applyCoupon(): void
    {
        $this->couponError = null;
        $this->couponMessage = null;

        $code = strtoupper(trim($this->coupon_code));

        if ($code === '') {
            $this->couponError = 'Escribe un código de cupón.';
            return;
        }

        $coupon = $this->company->coupons()
            ->active()
            ->where('scope', 'company')
            ->where('code', $code)
            ->first();

        if (! $coupon || ! $coupon->isAvailable()) {
            $this->couponError = 'Este cupón no está disponible o ya venció.';
            $this->appliedCouponId = null;
            return;
        }

        if ($coupon->calculateDiscount($this->pricing['subtotal'] - $this->pricing['promotion_discount']) <= 0) {
            $this->couponError = 'El cupón no aplica para este valor de reserva.';
            $this->appliedCouponId = null;
            return;
        }

        $this->coupon_code = $code;
        $this->appliedCouponId = $coupon->id;
        $this->couponMessage = 'Cupón aplicado: '.$coupon->discountLabel();
    }

    public function clearCoupon(): void
    {
        $this->coupon_code = '';
        $this->appliedCouponId = null;
        $this->couponMessage = null;
        $this->couponError = null;
    }

    public function back(): void
    {
        if ($this->step > 1) {
            $this->step--;
        }
    }

    /* ----------------------- Confirmación ----------------------- */

    public function confirm(): void
    {
        $this->validate([
            'customer_first_name' => 'required|string|max:100',
            'customer_phone'      => ['required', 'string', 'max:25', $this->whatsappPhoneRule()],
            'customer_email'      => 'required|email|max:255',
            'customer_notes'      => 'nullable|string|max:1000',
        ]);

        $this->error = null;
        $phone = $this->normalizeWhatsappPhone($this->customer_phone);

        try {
            $booking = app(BookingService::class)->create([
                'company_id'           => $this->company->id,
                'service_id'           => $this->serviceId,
                'employee_id'          => $this->resolvedEmployeeId,
                'booking_date'         => $this->date,
                'start_time'           => $this->time,
                'customer_first_name'  => $this->customer_first_name,
                'customer_last_name'   => $this->customer_last_name ?: null,
                'customer_phone'       => $phone,
                'customer_email'       => $this->customer_email,
                'customer_notes'       => $this->customer_notes ?: null,
                'promotion_id'         => $this->selectedPromotion?->id,
                'coupon_id'            => $this->appliedCoupon?->id,
                'discount_amount'      => $this->pricing['promotion_discount'] + $this->pricing['coupon_discount'],
                'source'               => 'public',
            ]);

            $this->confirmedCode = $booking->code;
            $this->confirmedCancelUrl = route('public.bookings.cancel.show', $booking);
            $this->step = 5;
        } catch (\Throwable $e) {
            $this->error = $e->getMessage();
            $this->step = 3; // vuelve al selector de hora
            $this->time = '';
        }
    }

    public function startNewBooking(): void
    {
        $this->serviceId = null;
        $this->employeeId = null;
        $this->anyEmployee = false;
        $this->date = now($this->company->timezone)->toDateString();
        $this->time = '';
        $this->resolvedEmployeeId = null;
        $this->customer_first_name = '';
        $this->customer_last_name = '';
        $this->customer_phone = '';
        $this->customer_email = '';
        $this->customer_notes = '';
        $this->countryCallingCode = $this->inferCountryCallingCode();
        $this->coupon_code = '';
        $this->appliedCouponId = null;
        $this->couponMessage = null;
        $this->couponError = null;
        $this->confirmedCode = null;
        $this->confirmedCancelUrl = null;
        $this->error = null;
        $this->step = 1;

        Tenant::set($this->company);
    }

    public function render()
    {
        return view('livewire.public.booking-wizard');
    }

    protected function whatsappPhoneRule(): \Closure
    {
        return function (string $attribute, mixed $value, \Closure $fail): void {
            $phone = trim((string) $value);
            $digits = preg_replace('/\D+/', '', $phone);

            if (str_starts_with($phone, '+')) {
                $countryCode = substr($digits, 0, -10);
                $whatsappNumber = substr($digits, -10);

                if (strlen($countryCode) < 1 || strlen($countryCode) > 3 || strlen($whatsappNumber) !== 10) {
                    $fail('El celular debe tener indicativo de país y un número de WhatsApp de 10 dígitos. Ej: '.$this->countryCallingCode.' 3001234567.');
                }

                return;
            }

            if (strlen($digits) !== 10) {
                $fail('Escribe el número de WhatsApp de 10 dígitos. El indicativo '.$this->countryCallingCode.' se agrega automáticamente.');
            }
        };
    }

    protected function normalizeWhatsappPhone(string $phone): string
    {
        $phone = trim($phone);
        $digits = preg_replace('/\D+/', '', $phone);

        if (str_starts_with($phone, '+')) {
            return '+'.$digits;
        }

        return $this->countryCallingCode.$digits;
    }

    protected function inferCountryCallingCode(): string
    {
        foreach ([$this->company->whatsapp, $this->company->phone] as $phone) {
            $raw = trim((string) $phone);
            $digits = preg_replace('/\D+/', '', $raw);

            if (str_starts_with($raw, '+') && strlen($digits) > 10) {
                $code = substr($digits, 0, -10);

                if (strlen($code) >= 1 && strlen($code) <= 3) {
                    return '+'.$code;
                }
            }
        }

        return match (strtolower(trim((string) $this->company->country))) {
            'colombia', 'co' => '+57',
            'estados unidos', 'usa', 'united states', 'us' => '+1',
            'méxico', 'mexico', 'mx' => '+52',
            'perú', 'peru', 'pe' => '+51',
            'ecuador', 'ec' => '+593',
            default => '+57',
        };
    }
}
