<?php

namespace App\Http\Controllers\Admin;

use App\Enums\AppointmentStatus;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreStaffProfileRequest;
use App\Http\Requests\UpdateStaffProfileRequest;
use App\Models\Appointment;
use App\Models\Service;
use App\Models\StaffProfile;
use App\Services\Tenancy\CurrentBusiness;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\View\View;

class StaffController extends Controller
{
    public function index(Request $request, CurrentBusiness $currentBusiness): View
    {
        $business = $currentBusiness->get() ?? abort(403);
        $filters = $request->only(['q', 'service_id', 'status', 'sort']);

        $staffQuery = StaffProfile::query()
            ->forBusiness($business)
            ->with(['services', 'workingHours'])
            ->withCount([
                'appointments as today_appointments_count' => fn (Builder $query) => $query->whereDate('starts_at', today()),
                'appointments as month_appointments_count' => fn (Builder $query) => $query->whereBetween('starts_at', [now()->startOfMonth(), now()->endOfMonth()]),
                'appointments as completed_appointments_count' => fn (Builder $query) => $query->where('status', AppointmentStatus::Completed->value),
            ])
            ->when($filters['q'] ?? null, function (Builder $query, string $search): void {
                $query->where(function (Builder $query) use ($search): void {
                    $query->where('name', 'like', "%{$search}%")
                        ->orWhere('role', 'like', "%{$search}%")
                        ->orWhere('email', 'like', "%{$search}%")
                        ->orWhere('phone', 'like', "%{$search}%")
                        ->orWhereHas('services', fn (Builder $serviceQuery) => $serviceQuery->where('name', 'like', "%{$search}%"));
                });
            })
            ->when($filters['service_id'] ?? null, function (Builder $query, string $serviceId): void {
                $query->whereHas('services', fn (Builder $serviceQuery) => $serviceQuery->whereKey($serviceId));
            })
            ->when($filters['status'] ?? null, function (Builder $query, string $status): void {
                if ($status === StaffProfile::STATUS_INACTIVE) {
                    $query->where('status', StaffProfile::STATUS_INACTIVE);

                    return;
                }

                $query->where('status', StaffProfile::STATUS_ACTIVE)
                    ->where('availability_status', $status);
            });

        match ($filters['sort'] ?? 'name') {
            'newest' => $staffQuery->latest(),
            'appointments_desc' => $staffQuery->orderByDesc('month_appointments_count'),
            default => $staffQuery->orderBy('name'),
        };

        $staffMembers = $staffQuery->paginate(10)->withQueryString();

        return view('admin.staff.index', [
            'staffMembers' => $staffMembers,
            'filters' => $filters,
            'services' => Service::query()->forBusiness($business)->orderBy('name')->get(),
            'stats' => [
                'total' => StaffProfile::query()->forBusiness($business)->count(),
                'available' => StaffProfile::query()->forBusiness($business)->active()->where('availability_status', StaffProfile::AVAILABILITY_AVAILABLE)->count(),
                'today_appointments' => Appointment::query()->forBusiness($business)->whereDate('starts_at', today())->count(),
                'average_rating' => $this->averageRating($business->id),
            ],
        ]);
    }

    public function create(CurrentBusiness $currentBusiness): View
    {
        $business = $currentBusiness->get() ?? abort(403);

        return view('admin.staff.create', [
            'staff' => new StaffProfile([
                'availability_status' => StaffProfile::AVAILABILITY_AVAILABLE,
                'status' => StaffProfile::STATUS_ACTIVE,
                'color' => '#2563EB',
            ]),
            'services' => Service::query()->forBusiness($business)->orderBy('name')->get(),
            'selectedServiceIds' => [],
        ]);
    }

    public function store(StoreStaffProfileRequest $request, CurrentBusiness $currentBusiness): RedirectResponse
    {
        $business = $currentBusiness->get() ?? abort(403);
        $payload = $request->validatedPayload();
        $payload['business_id'] = $business->id;

        if ($request->hasFile('avatar')) {
            $payload['avatar_path'] = $request->file('avatar')->store('staff', 'public');
        }

        $staff = StaffProfile::query()->create($payload);
        $this->syncServices($staff, $request->validated('service_ids', []), $business->id);
        $this->seedDefaultWorkingHours($staff, $business->id);

        return redirect()
            ->route('admin.staff.show', $staff)
            ->with('status', 'Staff profile created successfully.');
    }

    public function show(StaffProfile $staff, CurrentBusiness $currentBusiness): View
    {
        $business = $this->authorizeStaff($staff, $currentBusiness);
        $staff->load(['services', 'workingHours', 'breaks'])
            ->loadCount([
                'appointments as today_appointments_count' => fn (Builder $query) => $query->whereDate('starts_at', today()),
                'appointments as month_appointments_count' => fn (Builder $query) => $query->whereBetween('starts_at', [now()->startOfMonth(), now()->endOfMonth()]),
                'appointments as completed_appointments_count' => fn (Builder $query) => $query->where('status', AppointmentStatus::Completed->value),
            ]);

        $todayAppointments = Appointment::query()
            ->forBusiness($business)
            ->where('staff_profile_id', $staff->id)
            ->whereDate('starts_at', today())
            ->with(['customer', 'service'])
            ->orderBy('starts_at')
            ->get();

        $monthlyRevenue = Appointment::query()
            ->forBusiness($business)
            ->where('staff_profile_id', $staff->id)
            ->whereBetween('starts_at', [now()->startOfMonth(), now()->endOfMonth()])
            ->sum('price');

        return view('admin.staff.show', [
            'staff' => $staff,
            'todayAppointments' => $todayAppointments,
            'monthlyRevenue' => $monthlyRevenue,
            'workingHoursSummary' => $this->workingHoursSummary($staff),
        ]);
    }

    public function edit(StaffProfile $staff, CurrentBusiness $currentBusiness): View
    {
        $business = $this->authorizeStaff($staff, $currentBusiness);
        $staff->load('services');

        return view('admin.staff.edit', [
            'staff' => $staff,
            'services' => Service::query()->forBusiness($business)->orderBy('name')->get(),
            'selectedServiceIds' => $staff->services->pluck('id')->all(),
        ]);
    }

    public function update(UpdateStaffProfileRequest $request, StaffProfile $staff, CurrentBusiness $currentBusiness): RedirectResponse
    {
        $business = $this->authorizeStaff($staff, $currentBusiness);
        $payload = $request->validatedPayload();

        if ($request->hasFile('avatar')) {
            $payload['avatar_path'] = $request->file('avatar')->store('staff', 'public');
        }

        $staff->update($payload);
        $this->syncServices($staff, $request->validated('service_ids', []), $business->id);

        return redirect()
            ->route('admin.staff.show', $staff)
            ->with('status', 'Staff profile updated successfully.');
    }

    public function destroy(StaffProfile $staff, CurrentBusiness $currentBusiness): RedirectResponse
    {
        $this->authorizeStaff($staff, $currentBusiness);
        $staff->delete();

        return redirect()
            ->route('admin.staff.index')
            ->with('status', 'Staff profile deleted successfully.');
    }

    private function authorizeStaff(StaffProfile $staff, CurrentBusiness $currentBusiness)
    {
        $business = $currentBusiness->get() ?? abort(403);

        abort_unless((int) $staff->business_id === (int) $business->id, 404);

        return $business;
    }

    private function syncServices(StaffProfile $staff, array $serviceIds, int $businessId): void
    {
        $serviceIds = Service::query()
            ->where('business_id', $businessId)
            ->whereIn('id', $serviceIds)
            ->pluck('id')
            ->all();

        $syncPayload = collect($serviceIds)->mapWithKeys(fn (int $id) => [
            $id => ['business_id' => $businessId],
        ])->all();

        $staff->services()->sync($syncPayload);
    }

    private function seedDefaultWorkingHours(StaffProfile $staff, int $businessId): void
    {
        foreach (range(0, 6) as $day) {
            $staff->workingHours()->updateOrCreate(
                ['day_of_week' => $day],
                [
                    'business_id' => $businessId,
                    'starts_at' => $day === 0 ? null : '09:00',
                    'ends_at' => $day === 0 ? null : '17:00',
                    'is_closed' => $day === 0,
                ]
            );
        }
    }

    private function workingHoursSummary(StaffProfile $staff): array
    {
        $hours = $staff->workingHours->sortBy('day_of_week');
        $workingDays = $hours->where('is_closed', false)->count();

        return [
            'weekly_hours' => $hours->where('is_closed', false)->sum(function ($row): float {
                if (! $row->starts_at || ! $row->ends_at) {
                    return 0;
                }

                return Carbon::parse($row->starts_at)->diffInMinutes(Carbon::parse($row->ends_at)) / 60;
            }),
            'working_days' => $workingDays,
            'breaks' => $staff->breaks->count(),
        ];
    }

    private function averageRating(int $businessId): string
    {
        $ratings = StaffProfile::query()
            ->where('business_id', $businessId)
            ->get(['meta'])
            ->map(fn (StaffProfile $staff) => data_get($staff->meta, 'rating'))
            ->filter(fn ($rating) => is_numeric($rating))
            ->map(fn ($rating) => (float) $rating);

        return $ratings->isNotEmpty() ? number_format($ratings->avg(), 1) : 'N/A';
    }
}
