<?php

namespace App\Http\Controllers;

use App\Models\Branch;
use App\Models\Customer;
use App\Models\CustomerAppointment;
use App\Models\User;
use App\Services\AuditService;
use App\Services\BranchAccessService;
use App\Services\ManagementCalendarService;
use App\Support\Authorization\PermissionName;
use Carbon\CarbonImmutable;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;

class ManagementCalendarController extends Controller
{
    public function __construct(private BranchAccessService $branchAccess)
    {
    }

    public function index(Request $request, ManagementCalendarService $calendar): View
    {
        $allowed = $this->branchIds($request);
        $validated = validator([
            'branch_ids'=>$request->input('branch_ids',[]),'from'=>$request->input('from'),'to'=>$request->input('to'),
            'type'=>$request->input('type'),
        ],[
            'branch_ids'=>['array'],'branch_ids.*'=>['integer','exists:branches,id'],'from'=>['nullable','date'],'to'=>['nullable','date'],
            'type'=>['nullable',Rule::in(array_keys(ManagementCalendarService::TYPES))],
        ])->validate();

        $requested = collect($validated['branch_ids'] ?? [])->map(fn ($id)=>(int)$id)->unique()->values();
        if ($requested->diff($allowed)->isNotEmpty()) abort(403,'One or more selected branches are outside your assigned scope.');
        $selected = $requested->isEmpty() ? $allowed : $requested;

        $from = CarbonImmutable::parse($validated['from'] ?? now()->startOfMonth())->startOfDay();
        $to = CarbonImmutable::parse($validated['to'] ?? now()->endOfMonth())->endOfDay();
        if ($to->lt($from)) throw ValidationException::withMessages(['to'=>'Calendar end date cannot be before the start date.']);
        if ($from->diffInDays($to) > 366) throw ValidationException::withMessages(['to'=>'Calendar range cannot exceed 366 days.']);

        $events = collect($calendar->events($selected->all(),$from->toDateString(),$to->toDateString()));
        if (! empty($validated['type'])) $events = $events->where('type',$validated['type'])->values();

        $branches = Branch::whereIn('id',$allowed)->orderBy('name')->get();
        $staff = $this->staff($allowed->all());
        $customers = Customer::query()->whereIn('branch_id',$allowed)->where('status','active')->orderBy('first_name')->orderBy('last_name')->get();

        return view('calendar.index',[
            'events'=>$events,'eventsByDate'=>$events->groupBy('date'),'branches'=>$branches,'selectedBranchIds'=>$selected,
            'staff'=>$staff,'customers'=>$customers,'from'=>$from->toDateString(),'to'=>$to->toDateString(),
            'types'=>ManagementCalendarService::TYPES,'selectedType'=>$validated['type'] ?? '',
        ]);
    }

    public function storeAppointment(Request $request): RedirectResponse
    {
        Gate::forUser($request->user())->authorize(PermissionName::APPOINTMENTS_CREATE->value);
        $data = $request->validate([
            'branch_id'=>['required','integer','exists:branches,id'],'customer_id'=>['required','integer','exists:customers,id'],
            'staff_id'=>['nullable','integer','exists:users,id'],'title'=>['required','string','max:255'],'purpose'=>['nullable','string','max:255'],
            'scheduled_at'=>['required','date'],'notes'=>['nullable','string','max:5000'],
        ]);
        $this->authorizeAppointmentSelections($request,(int)$data['branch_id'],(int)$data['customer_id'],$data['staff_id'] ?? null);
        $appointment = CustomerAppointment::create(array_merge($data,['status'=>'scheduled','created_by'=>$request->user()->id,'updated_by'=>$request->user()->id]));
        AuditService::record('appointment.created',$appointment,[],$appointment->toArray());
        return back()->with('success','Customer appointment added to the calendar.');
    }

    public function appointmentStatus(Request $request, CustomerAppointment $appointment): RedirectResponse
    {
        Gate::forUser($request->user())->authorize('manageStatus', $appointment);
        $data = $request->validate(['status'=>['required',Rule::in(CustomerAppointment::STATUSES)]]);
        $before = $appointment->toArray();
        $appointment->update(['status'=>$data['status'],'updated_by'=>$request->user()->id]);
        AuditService::record('appointment.status_changed',$appointment,$before,$appointment->fresh()->toArray());
        return back()->with('success','Appointment status updated.');
    }

    private function authorizeAppointmentSelections(Request $request, int $branchId, int $customerId, mixed $staffId): void
    {
        $this->branchAccess->assertBranchAccessible($request->user(),$branchId);
        abort_unless(Customer::query()->whereKey($customerId)->where('branch_id',$branchId)->exists(),422,'Customer must belong to the selected branch.');
        if ($staffId) {
            $staff = User::query()->whereKey((int)$staffId)->where('status','active')->first();
            abort_unless($staff && $this->branchAccess->isAssignedToBranch($staff,$branchId),422,'Staff member must belong to the selected branch.');
        }
    }

    private function branchIds(Request $request)
    {
        return collect($this->branchAccess->accessibleBranchIds($request->user()));
    }

    private function staff(array $branchIds)
    {
        return User::query()->where('status','active')
            ->where(function ($query) use ($branchIds): void {
                $query->whereIn('primary_branch_id',$branchIds)
                    ->orWhereHas('branches',fn ($branches) => $branches->whereIn('branches.id',$branchIds));
            })
            ->orderBy('name')->get();
    }
}
