<?php

namespace App\Services;

use App\Models\Appointment;
use App\Models\AppointmentService as AppointmentServiceModel;
use App\Models\Customer;
use App\Models\Service;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class AppointmentService
{
    public function __construct(
        protected AppointmentSlotService $slotService,
        protected BookingConflictService $conflictService,
        protected CustomerService $customerService
    ) {
    }

    public function generateAppointmentNo(): string
    {
        $today = now()->format('Ymd');
        $last = Appointment::whereDate('created_at', today())->orderByDesc('id')->first();
        $num = $last ? (int) Str::afterLast($last->appointment_no, '-') + 1 : 1;
        return 'APT-' . $today . '-' . str_pad((string) $num, 4, '0', STR_PAD_LEFT);
    }

    public function paginate(array $filters = [], ?int $branchId = null, int $perPage = 15): LengthAwarePaginator
    {
        $query = Appointment::query()->with(['branch', 'customer', 'appointmentServices.service', 'appointmentServices.assignedStaff']);

        if ($branchId) {
            $query->where('branch_id', $branchId);
        }

        $query->when(Arr::get($filters, 'search'), function (Builder $q, $search) {
            $q->where(function (Builder $q2) use ($search) {
                $q2->where('appointment_no', 'like', "%{$search}%")
                    ->orWhereHas('customer', fn ($c) => $c->where('first_name', 'like', "%{$search}%")
                        ->orWhere('last_name', 'like', "%{$search}%")
                        ->orWhere('mobile', 'like', "%{$search}%"));
            });
        })
            ->when(Arr::get($filters, 'branch_id'), fn (Builder $q, $id) => $q->where('branch_id', $id))
            ->when(Arr::get($filters, 'status'), fn (Builder $q, $s) => $q->where('status', $s))
            ->when(Arr::get($filters, 'date'), fn (Builder $q, $date) => $q->where('appointment_date', $date))
            ->when(Arr::get($filters, 'date_from'), fn (Builder $q, $date) => $q->where('appointment_date', '>=', $date))
            ->when(Arr::get($filters, 'date_to'), fn (Builder $q, $date) => $q->where('appointment_date', '<=', $date))
            ->orderByDesc('appointment_date')
            ->orderBy('start_time');

        return $query->paginate($perPage)->withQueryString();
    }

    public function create(array $data): Appointment
    {
        return DB::transaction(function () use ($data) {
            $customerId = $data['customer_id'] ?? null;
            if (empty($customerId) && !empty($data['quick_customer'])) {
                $customer = $this->customerService->create(array_merge($data['quick_customer'], [
                    'branch_id' => $data['branch_id'],
                    'customer_type' => 'new',
                    'status' => 'active',
                ]));
                $customerId = $customer->id;
            }
            $items = $data['services']; // [ ['service_id'=>, 'assigned_staff_id'=>?, 'price'=>?, 'duration_minutes'=>? ], ... ]
            $startTime = $data['start_time'];
            $slots = $this->slotService->calculateServiceSlots($data['appointment_date'], $startTime, $items);
            $endTime = end($slots)['end_time'];

            $conflicts = $this->conflictService->getConflictingStaffIds($data['branch_id'], $data['appointment_date'], $slots);
            if (!empty($conflicts)) {
                throw new \InvalidArgumentException('One or more staff have conflicting bookings for the selected time.');
            }

            $appointment = Appointment::create([
                'appointment_no' => $this->generateAppointmentNo(),
                'branch_id' => $data['branch_id'],
                'customer_id' => $customerId,
                'appointment_date' => $data['appointment_date'],
                'start_time' => $startTime,
                'end_time' => $endTime,
                'booking_source' => $data['booking_source'] ?? 'admin',
                'status' => $data['status'] ?? 'pending',
                'notes' => $data['notes'] ?? null,
                'created_by' => auth()->id(),
                'updated_by' => auth()->id(),
            ]);

            foreach ($slots as $slot) {
                AppointmentServiceModel::create([
                    'appointment_id' => $appointment->id,
                    'service_id' => $slot['service_id'],
                    'assigned_staff_id' => $slot['assigned_staff_id'],
                    'price' => $slot['price'],
                    'duration_minutes' => $slot['duration_minutes'],
                    'start_time' => $slot['start_time'],
                    'end_time' => $slot['end_time'],
                    'status' => 'pending',
                ]);
            }

            return $appointment->load('appointmentServices');
        });
    }

    public function update(Appointment $appointment, array $data): Appointment
    {
        if (in_array($appointment->status, ['cancelled', 'completed', 'no_show'])) {
            throw new \InvalidArgumentException('This appointment cannot be edited.');
        }
        return DB::transaction(function () use ($appointment, $data) {
            if (!empty($data['appointment_date']) && !empty($data['start_time']) && !empty($data['services'])) {
                $slots = $this->slotService->calculateServiceSlots(
                    $data['appointment_date'],
                    $data['start_time'],
                    $data['services']
                );
                $conflicts = $this->conflictService->getConflictingStaffIds(
                    $appointment->branch_id,
                    $data['appointment_date'],
                    $slots
                );
                if (!empty($conflicts)) {
                    throw new \InvalidArgumentException('One or more staff have conflicting bookings.');
                }
                $appointment->appointmentServices()->delete();
                foreach ($slots as $slot) {
                    AppointmentServiceModel::create([
                        'appointment_id' => $appointment->id,
                        'service_id' => $slot['service_id'],
                        'assigned_staff_id' => $slot['assigned_staff_id'],
                        'price' => $slot['price'],
                        'duration_minutes' => $slot['duration_minutes'],
                        'start_time' => $slot['start_time'],
                        'end_time' => $slot['end_time'],
                        'status' => 'pending',
                    ]);
                }
                $appointment->update([
                    'appointment_date' => $data['appointment_date'],
                    'start_time' => $data['start_time'],
                    'end_time' => end($slots)['end_time'],
                    'updated_by' => auth()->id(),
                ]);
            }
            if (isset($data['notes'])) {
                $appointment->update(['notes' => $data['notes']]);
            }
            return $appointment->fresh(['appointmentServices.service', 'appointmentServices.assignedStaff']);
        });
    }

    public function checkIn(Appointment $appointment): Appointment
    {
        $appointment->update([
            'status' => 'checked_in',
            'checked_in_at' => now(),
            'updated_by' => auth()->id(),
        ]);
        return $appointment->fresh();
    }

    public function complete(Appointment $appointment): Appointment
    {
        $appointment->update([
            'status' => 'completed',
            'completed_at' => now(),
            'updated_by' => auth()->id(),
        ]);
        $appointment->appointmentServices()->update(['status' => 'completed']);
        $customer = $appointment->customer;
        $customer->increment('visit_count');
        $customer->update(['last_visit_at' => now()]);
        return $appointment->fresh();
    }

    public function cancel(Appointment $appointment, string $reason): Appointment
    {
        $appointment->update([
            'status' => 'cancelled',
            'cancellation_reason' => $reason,
            'updated_by' => auth()->id(),
        ]);
        return $appointment->fresh();
    }

    public function markNoShow(Appointment $appointment): Appointment
    {
        $appointment->update([
            'status' => 'no_show',
            'updated_by' => auth()->id(),
        ]);
        return $appointment->fresh();
    }
}
