<?php

namespace App\Services\Booking;

use App\Enums\AppointmentStatus;
use App\Models\Appointment;
use App\Models\Business;
use App\Models\Customer;
use App\Models\Service;
use App\Models\StaffProfile;
use App\Services\Appointments\AppointmentService;
use App\Services\Appointments\AvailabilityService;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;

class BookingService
{
    public function __construct(
        private readonly AppointmentService $appointmentService,
        private readonly AvailabilityService $availabilityService
    ) {
    }

    public function businessBySlug(string $slug): Business
    {
        return Business::query()
            ->active()
            ->where('slug', $slug)
            ->firstOrFail();
    }

    public function activeServices(Business $business): EloquentCollection
    {
        return Service::query()
            ->forBusiness($business)
            ->where('status', Service::STATUS_ACTIVE)
            ->with('staffProfiles')
            ->orderBy('sort_order')
            ->orderBy('name')
            ->get();
    }

    public function availableStaff(Business $business, ?Service $service = null): EloquentCollection
    {
        $query = StaffProfile::query()
            ->forBusiness($business)
            ->where('status', StaffProfile::STATUS_ACTIVE)
            ->orderBy('name');

        if ($service && $service->staffProfiles()->exists()) {
            $query->whereHas('services', fn ($serviceQuery) => $serviceQuery->whereKey($service->id));
        }

        return $query->get();
    }

    public function defaultStaffFor(Business $business, Service $service): ?StaffProfile
    {
        return $this->availableStaff($business, $service)->first();
    }

    public function dateOptions(Business $business, Service $service, StaffProfile $staff, int $days = 7): Collection
    {
        return collect(range(0, $days - 1))->map(function (int $offset) use ($business, $service, $staff): array {
            $date = today()->addDays($offset);
            $slots = $this->availabilityService->availableSlots($business, $service, $staff, $date);

            return [
                'date' => $date,
                'value' => $date->toDateString(),
                'slots_count' => count($slots),
            ];
        });
    }

    public function groupedSlots(Business $business, Service $service, StaffProfile $staff, Carbon $date): array
    {
        $slots = $this->availabilityService->availableSlots($business, $service, $staff, $date);
        $groups = [
            'Morning' => [],
            'Afternoon' => [],
            'Evening' => [],
        ];

        foreach ($slots as $slot) {
            $hour = (int) Carbon::parse($date->toDateString().' '.$slot)->format('H');
            $label = Carbon::parse($date->toDateString().' '.$slot)->format('g:i A');

            if ($hour < 12) {
                $groups['Morning'][$slot] = $label;
            } elseif ($hour < 17) {
                $groups['Afternoon'][$slot] = $label;
            } else {
                $groups['Evening'][$slot] = $label;
            }
        }

        return array_filter($groups);
    }

    public function findOrCreateCustomer(Business $business, array $data): Customer
    {
        $query = Customer::query()->forBusiness($business);

        $customer = null;
        if (! empty($data['customer_email'])) {
            $customer = (clone $query)->where('email', $data['customer_email'])->first();
        }
        if (! $customer && ! empty($data['customer_phone'])) {
            $customer = (clone $query)->where('phone', $data['customer_phone'])->first();
        }

        if (! $customer) {
            return Customer::query()->create([
                'business_id' => $business->id,
                'name' => $data['customer_name'],
                'phone' => $data['customer_phone'],
                'email' => $data['customer_email'] ?? null,
                'status' => Customer::STATUS_NEW,
                'source' => 'Public booking page',
                'notes' => $data['notes'] ?? null,
                'meta' => [
                    'notes_history' => [],
                ],
            ]);
        }

        $customer->update([
            'name' => $data['customer_name'],
            'phone' => $data['customer_phone'],
            'email' => $data['customer_email'] ?? $customer->email,
            'notes' => $data['notes'] ?? $customer->notes,
        ]);

        return $customer;
    }

    public function createBooking(Business $business, array $selection, array $customerData): Appointment
    {
        $service = $this->service($business, (int) $selection['service_id']);
        $staff = $this->staff($business, (int) $selection['staff_profile_id'], $service);
        $customer = $this->findOrCreateCustomer($business, $customerData);

        return $this->appointmentService->create($business, [
            'customer_id' => $customer->id,
            'service_id' => $service->id,
            'staff_profile_id' => $staff->id,
            'appointment_date' => $selection['appointment_date'],
            'appointment_time' => $selection['appointment_time'],
            'status' => $this->autoConfirm($business) ? AppointmentStatus::Confirmed->value : AppointmentStatus::Pending->value,
            'price' => $service->price,
            'payment_status' => 'unpaid',
            'customer_notes' => $customerData['notes'] ?? null,
            'internal_notes' => 'Created from public booking page.',
        ]);
    }

    public function reschedule(Business $business, Appointment $appointment, array $data): Appointment
    {
        return $this->appointmentService->update($business, $appointment, [
            'customer_id' => $appointment->customer_id,
            'service_id' => $appointment->service_id,
            'staff_profile_id' => $appointment->staff_profile_id,
            'appointment_date' => $data['appointment_date'],
            'appointment_time' => $data['appointment_time'],
            'status' => $appointment->status_value,
            'price' => $appointment->price,
            'payment_status' => $appointment->payment_status,
            'customer_notes' => $appointment->customer_notes,
            'internal_notes' => trim(($appointment->internal_notes ? $appointment->internal_notes."\n" : '').'Rescheduled from public booking page.'),
        ]);
    }

    public function cancel(Business $business, Appointment $appointment, ?string $reason = null, ?string $note = null): Appointment
    {
        $message = trim(collect([$reason, $note])->filter()->join(' - ')) ?: 'Cancelled from public booking page.';

        return $this->appointmentService->changeStatus($business, $appointment, AppointmentStatus::Cancelled->value, $message);
    }

    public function appointmentByToken(Business $business, string $token, string $type): Appointment
    {
        $column = $type === 'cancel' ? 'cancel_token' : 'reschedule_token';

        return Appointment::query()
            ->forBusiness($business)
            ->where($column, $token)
            ->with(['customer', 'service', 'staffProfile'])
            ->firstOrFail();
    }

    public function service(Business $business, int $serviceId): Service
    {
        return Service::query()
            ->forBusiness($business)
            ->where('status', Service::STATUS_ACTIVE)
            ->findOrFail($serviceId);
    }

    public function staff(Business $business, int|string|null $staffId, Service $service): StaffProfile
    {
        if ($staffId === null || $staffId === '' || $staffId === 'any') {
            return $this->defaultStaffFor($business, $service) ?? abort(404);
        }

        $staff = StaffProfile::query()
            ->forBusiness($business)
            ->where('status', StaffProfile::STATUS_ACTIVE)
            ->findOrFail($staffId);

        if ($service->staffProfiles()->exists() && ! $service->staffProfiles()->whereKey($staff->id)->exists()) {
            abort(404);
        }

        return $staff;
    }

    public function nextAvailableSuggestion(Business $business, Service $service, StaffProfile $staff): ?array
    {
        foreach (range(0, 14) as $offset) {
            $date = today()->addDays($offset);
            $slots = $this->availabilityService->availableSlots($business, $service, $staff, $date);

            if ($slots) {
                $time = $slots[0];

                return [
                    'date' => $date,
                    'time' => $time,
                    'label' => $date->format('M d, Y').' at '.Carbon::parse($date->toDateString().' '.$time)->format('g:i A'),
                ];
            }
        }

        return null;
    }

    public function businessView(Business $business): array
    {
        return [
            'name' => $business->name,
            'logo' => strtoupper(substr($business->name, 0, 1)),
            'cover' => $business->cover_path ? asset('storage/'.$business->cover_path) : 'https://images.unsplash.com/photo-1560066984-138dadb4c035?auto=format&fit=crop&w=1200&q=80',
            'rating' => '4.9',
            'reviews' => '120 reviews',
            'address' => $business->address ?: 'Business address not set',
            'phone' => $business->phone ?: 'Phone not set',
            'hours' => 'Open today',
            'description' => $business->description ?: 'Book appointments online with fast confirmation and friendly service.',
        ];
    }

    public function serviceCards(iterable $services): array
    {
        return collect($services)->map(fn (Service $service): array => [
            'id' => $service->id,
            'name' => $service->name,
            'description' => $service->description ?: 'Professional service available for online booking.',
            'duration' => $service->duration_minutes.' min',
            'price' => '$'.number_format((float) $service->price, 2),
            'image' => $service->image_url ?: 'https://images.unsplash.com/photo-1522337660859-02fbefca4702?auto=format&fit=crop&w=700&q=80',
        ])->all();
    }

    public function staffCards(iterable $staff): array
    {
        return collect($staff)->map(fn (StaffProfile $member): array => [
            'id' => $member->id,
            'name' => $member->name,
            'role' => $member->role ?: 'Service Professional',
            'status' => $member->availability_label,
            'image' => $member->avatar_url ?: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=300&q=80',
        ])->all();
    }

    public function selectedSummary(Business $business, ?Service $service = null, ?StaffProfile $staff = null, ?string $date = null, ?string $time = null, ?array $customer = null, ?Appointment $appointment = null): array
    {
        if ($time && preg_match('/^\d{1,2}$/', $time)) {
            $time = str_pad($time, 2, '0', STR_PAD_LEFT).':00';
        }

        $selectedDate = $date ? Carbon::parse($date)->format('M d, Y') : 'Choose date';
        $selectedTime = $time ? Carbon::parse(($date ?: today()->toDateString()).' '.$time)->format('g:i A') : 'Choose time';

        return [
            'booking_id' => $appointment?->booking_number ?? 'Pending',
            'service' => $service?->name ?? $appointment?->service?->name ?? 'Choose service',
            'staff' => $staff?->name ?? $appointment?->staffProfile?->name ?? 'Choose staff',
            'date' => $appointment?->starts_at?->format('M d, Y') ?? $selectedDate,
            'time' => $appointment?->starts_at?->format('g:i A') ?? $selectedTime,
            'duration' => $service ? $service->duration_minutes.' min' : ($appointment ? $appointment->duration_minutes.' min' : 'Duration'),
            'price' => $service ? '$'.number_format((float) $service->price, 2) : ($appointment ? '$'.number_format((float) $appointment->price, 2) : '$0.00'),
            'customer' => $customer['customer_name'] ?? $appointment?->customer?->name ?? 'Customer details',
            'reschedule_token' => $appointment?->reschedule_token,
            'cancel_token' => $appointment?->cancel_token,
        ];
    }

    private function autoConfirm(Business $business): bool
    {
        return (bool) $this->availabilityService->bookingSetting($business, 'auto_confirm_bookings', true);
    }
}
