<?php

namespace App\Controllers;

use App\Models\ShiftModel;
use App\Models\ShiftSwapRequestModel;
use App\Models\TimeOffRequestModel;
use App\Models\UserModel;
use Config\Lookups;

/**
 * Full workforce management (Phase 4, per explicit user choice over the
 * simpler "assignment calendar only" option): an assignment grid plus
 * staff-initiated shift-swap requests, time-off requests, and overtime
 * tracking, all with a manager approval step. Reachable by every role
 * (see Config\Lookups::$permissionModules['shifts']) — everyone needs to
 * see their own schedule and ask for a swap/time off, but only a manager
 * assigns shifts or approves/denies requests.
 */
class ShiftController extends BaseController
{
    public function index()
    {
        return session('userRole') === 'manager' ? $this->managerView() : $this->myShifts();
    }

    /** The week grid every manager uses to build the schedule, plus the requests waiting on their approval. */
    private function managerView()
    {
        $start = $this->request->getGet('start') ?: $this->mondayOf(date('Y-m-d'));

        return view('shifts/index', [
            'pageTitle'      => 'Shift Schedule',
            'start'          => $start,
            'dateColumns'    => $this->weekDates($start),
            'shifts'         => (new ShiftModel())->forWeek($start),
            'staff'          => (new UserModel())->where('is_active', 1)->orderBy('full_name')->findAll(),
            'pendingSwaps'   => (new ShiftSwapRequestModel())->pending(),
            'pendingTimeOff' => (new TimeOffRequestModel())->pending(),
            'lookups'        => new Lookups(),
        ]);
    }

    /** "My Shifts" — every non-manager role's own view: upcoming schedule + swap/time-off requests. */
    private function myShifts()
    {
        $userId = $this->currentUserId();

        return view('shifts/mine', [
            'pageTitle'   => 'My Shifts',
            'shifts'      => (new ShiftModel())->forUser($userId, date('Y-m-d', strtotime('-7 days'))),
            'colleagues'  => (new UserModel())->where('is_active', 1)->where('id !=', $userId)->orderBy('full_name')->findAll(),
            'swapRequests'    => (new ShiftSwapRequestModel())->forUser($userId),
            'timeOffRequests' => (new TimeOffRequestModel())->forUser($userId),
            'lookups'     => new Lookups(),
        ]);
    }

    private function mondayOf(string $date): string
    {
        return date('Y-m-d', strtotime('monday this week', strtotime($date)));
    }

    private function weekDates(string $start): array
    {
        return array_map(static fn ($i) => date('Y-m-d', strtotime($start . " +{$i} days")), range(0, 6));
    }

    /** Manager assigns a shift. Blocked if the staff member has approved time off that day, or is already scheduled for that block. */
    public function assign()
    {
        $userId  = (int) $this->request->getPost('user_id');
        $date    = $this->request->getPost('shift_date');
        $block   = $this->request->getPost('shift_block');
        $hours   = $this->request->getPost('standard_hours') ?: 8;

        if ($userId <= 0 || ! $date || ! array_key_exists($block, (new Lookups())->shiftBlocks)) {
            return redirect()->back()->with('error', 'Choose a staff member, date, and shift block.');
        }

        if ((new TimeOffRequestModel())->hasApprovedOverlap($userId, $date)) {
            return redirect()->back()->with('error', 'That staff member has approved time off on this date.');
        }

        $shifts = new ShiftModel();

        if ($shifts->where('user_id', $userId)->where('shift_date', $date)->where('shift_block', $block)->first() !== null) {
            return redirect()->back()->with('error', 'That staff member is already scheduled for this block.');
        }

        $shifts->insert([
            'user_id'        => $userId,
            'shift_date'     => $date,
            'shift_block'    => $block,
            'standard_hours' => $hours,
            'status'         => 'scheduled',
            'notes'          => $this->request->getPost('notes') ?: null,
            'created_by'     => $this->currentUserId(),
        ]);

        $staffName = (new UserModel())->find($userId)->full_name ?? "user #{$userId}";
        $this->logActivity('staff', "Scheduled {$staffName} for {$block} on {$date}.");

        return redirect()->back()->with('success', 'Shift assigned.');
    }

    public function cancelShift(int $id)
    {
        $shifts = new ShiftModel();
        $shift  = $shifts->find($id);

        if ($shift === null) {
            return redirect()->back()->with('error', 'Shift not found.');
        }

        $shifts->update($id, ['status' => 'cancelled']);
        $this->logActivity('staff', "Cancelled shift #{$id} ({$shift->shift_date} {$shift->shift_block}).");

        return redirect()->back()->with('success', 'Shift cancelled.');
    }

    /** The shift's own staff member logs actual hours after working it; a manager can log on anyone's behalf. */
    public function logHours(int $id)
    {
        $shifts = new ShiftModel();
        $shift  = $shifts->find($id);

        if ($shift === null) {
            return redirect()->back()->with('error', 'Shift not found.');
        }

        $isManager = session('userRole') === 'manager';

        if (! $isManager && (int) $shift->user_id !== $this->currentUserId()) {
            return redirect()->back()->with('error', 'You can only log hours for your own shifts.');
        }

        $hours = $this->request->getPost('actual_hours');

        if ($hours === null || $hours === '' || (float) $hours < 0) {
            return redirect()->back()->with('error', 'Enter the hours actually worked.');
        }

        $shifts->update($id, ['actual_hours' => $hours, 'status' => 'completed']);

        $overtime = max(0.0, (float) $hours - (float) $shift->standard_hours);
        $this->logActivity('staff', "Logged {$hours}h for shift #{$id} ({$shift->shift_date} {$shift->shift_block})" . ($overtime > 0 ? sprintf(', %.2fh overtime.', $overtime) : '.'));

        return redirect()->back()->with('success', 'Hours logged.');
    }

    /** Staff-initiated — only the shift's own owner can request a swap of it. */
    public function requestSwap(int $shiftId)
    {
        $shift = (new ShiftModel())->find($shiftId);

        if ($shift === null || (int) $shift->user_id !== $this->currentUserId()) {
            return redirect()->back()->with('error', 'You can only request a swap for your own shift.');
        }

        $targetUserId = (int) $this->request->getPost('target_user_id');

        if ($targetUserId <= 0) {
            return redirect()->back()->with('error', 'Choose who you\'d like to take this shift.');
        }

        (new ShiftSwapRequestModel())->insert([
            'requesting_shift_id' => $shiftId,
            'requested_by'        => $this->currentUserId(),
            'target_user_id'      => $targetUserId,
            'reason'              => $this->request->getPost('reason') ?: null,
            'status'              => 'pending',
        ]);

        $this->logActivity('staff', "Requested a shift swap for shift #{$shiftId} ({$shift->shift_date} {$shift->shift_block}).");

        return redirect()->back()->with('success', 'Swap request sent for manager approval.');
    }

    /** Approving reassigns the shift to the target staff member — blocked if that collides with an existing assignment. */
    public function approveSwap(int $id)
    {
        $requests = new ShiftSwapRequestModel();
        $req      = $requests->find($id);

        if ($req === null || $req->status !== 'pending') {
            return redirect()->back()->with('error', 'Request not found or already reviewed.');
        }

        $shifts = new ShiftModel();
        $shift  = $shifts->find($req->requesting_shift_id);

        if ($shift === null) {
            return redirect()->back()->with('error', 'The underlying shift no longer exists.');
        }

        if ($shifts->where('user_id', $req->target_user_id)->where('shift_date', $shift->shift_date)->where('shift_block', $shift->shift_block)->first() !== null) {
            return redirect()->back()->with('error', 'That staff member is already scheduled for this block — deny and ask them to pick another shift.');
        }

        $shifts->update($shift->id, ['user_id' => $req->target_user_id]);
        $requests->update($id, ['status' => 'approved', 'reviewed_by' => $this->currentUserId(), 'reviewed_at' => date('Y-m-d H:i:s')]);

        $this->logActivity('staff', "Approved shift swap #{$id} — shift #{$shift->id} reassigned.");

        return redirect()->back()->with('success', 'Swap approved and shift reassigned.');
    }

    public function denySwap(int $id)
    {
        $requests = new ShiftSwapRequestModel();

        if ($requests->find($id) === null) {
            return redirect()->back()->with('error', 'Request not found.');
        }

        $requests->update($id, ['status' => 'denied', 'reviewed_by' => $this->currentUserId(), 'reviewed_at' => date('Y-m-d H:i:s')]);
        $this->logActivity('staff', "Denied shift swap request #{$id}.");

        return redirect()->back()->with('success', 'Swap denied.');
    }

    /** Staff-initiated — any logged-in staff member can ask for time off. */
    public function requestTimeOff()
    {
        $dateFrom = $this->request->getPost('date_from');
        $dateTo   = $this->request->getPost('date_to');

        if (! $dateFrom || ! $dateTo || $dateTo < $dateFrom) {
            return redirect()->back()->with('error', 'Enter a valid date range.');
        }

        (new TimeOffRequestModel())->insert([
            'user_id'   => $this->currentUserId(),
            'date_from' => $dateFrom,
            'date_to'   => $dateTo,
            'reason'    => $this->request->getPost('reason') ?: null,
            'status'    => 'pending',
        ]);

        $this->logActivity('staff', "Requested time off {$dateFrom} to {$dateTo}.");

        return redirect()->back()->with('success', 'Time-off request sent for manager approval.');
    }

    public function approveTimeOff(int $id)
    {
        $requests = new TimeOffRequestModel();
        $req      = $requests->find($id);

        if ($req === null || $req->status !== 'pending') {
            return redirect()->back()->with('error', 'Request not found or already reviewed.');
        }

        $requests->update($id, ['status' => 'approved', 'reviewed_by' => $this->currentUserId(), 'reviewed_at' => date('Y-m-d H:i:s')]);
        $this->logActivity('staff', "Approved time off #{$id} ({$req->date_from} to {$req->date_to}).");

        return redirect()->back()->with('success', 'Time off approved.');
    }

    public function denyTimeOff(int $id)
    {
        $requests = new TimeOffRequestModel();

        if ($requests->find($id) === null) {
            return redirect()->back()->with('error', 'Request not found.');
        }

        $requests->update($id, ['status' => 'denied', 'reviewed_by' => $this->currentUserId(), 'reviewed_at' => date('Y-m-d H:i:s')]);
        $this->logActivity('staff', "Denied time-off request #{$id}.");

        return redirect()->back()->with('success', 'Time off denied.');
    }
}
