<?php

namespace App\Modules\HR\Leave\Services;

use App\Modules\HR\Leave\Events\LeaveRequestHrApproved;
use App\Modules\HR\Leave\Events\LeaveRequestManagerApproved;
use App\Modules\HR\Leave\Events\LeaveRequestRejected;
use App\Modules\HR\Leave\Models\LeaveRequest;
use App\Modules\HR\Leave\Repositories\LeaveRequestRepository;
use App\Modules\HR\Leave\Support\LeaveRequestStatus;
use App\Models\User;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Facades\DB;

class LeaveApprovalService
{
    public function __construct(
        private readonly LeaveRequestRepository $requests,
        private readonly LeaveAttendanceSyncService $attendanceSync,
        private readonly LeaveBalanceService $balances,
        private readonly Dispatcher $events,
    ) {}

    public function approveManager(LeaveRequest $request, User $approver): LeaveRequest
    {
        return DB::transaction(function () use ($request, $approver): LeaveRequest {
            $locked = $this->requests->lockForTenant((int) $request->tenant_id, (int) $request->id);
            if ($locked === null) {
                abort(404);
            }
            if ($locked->status !== LeaveRequestStatus::PENDING) {
                return $locked;
            }
            $updated = $this->requests->update($locked, [
                'status' => LeaveRequestStatus::MANAGER_APPROVED,
                'manager_approved_by' => $approver->id,
                'manager_approved_at' => now(),
            ]);
            DB::afterCommit(function () use ($updated, $approver): void {
                $this->events->dispatch(new LeaveRequestManagerApproved($updated, $approver->id));
            });

            return $updated;
        });
    }

    public function approveHr(LeaveRequest $request, User $approver): LeaveRequest
    {
        return DB::transaction(function () use ($request, $approver): LeaveRequest {
            $locked = $this->requests->lockForTenant((int) $request->tenant_id, (int) $request->id);
            if ($locked === null) {
                abort(404);
            }
            if ($locked->status !== LeaveRequestStatus::MANAGER_APPROVED) {
                return $locked;
            }

            return $this->finalizeHrApproval($locked, $approver);
        });
    }

    /**
     * Skip manager/HR workflow when the leave type does not require approval.
     */
    public function autoApprove(LeaveRequest $request, User $actor): LeaveRequest
    {
        return DB::transaction(function () use ($request, $actor): LeaveRequest {
            $locked = $this->requests->lockForTenant((int) $request->tenant_id, (int) $request->id);
            if ($locked === null) {
                abort(404);
            }
            if ($locked->status !== LeaveRequestStatus::PENDING) {
                return $locked;
            }

            return $this->finalizeHrApproval($locked, $actor);
        });
    }

    private function finalizeHrApproval(LeaveRequest $locked, User $approver): LeaveRequest
    {
        $updated = $this->requests->update($locked, [
            'status' => LeaveRequestStatus::HR_APPROVED,
            'hr_approved_by' => $approver->id,
            'hr_approved_at' => now(),
            'approved_by' => $approver->id,
            'approved_at' => now(),
        ]);
        $updated->loadMissing('leaveType');
        $this->balances->consume($updated);
        $this->attendanceSync->applyApprovedLeave($updated);
        DB::afterCommit(function () use ($updated, $approver): void {
            $this->events->dispatch(new LeaveRequestHrApproved($updated, $approver->id));
        });

        return $updated;
    }

    public function reject(LeaveRequest $request, User $approver): LeaveRequest
    {
        return DB::transaction(function () use ($request, $approver): LeaveRequest {
            $locked = $this->requests->lockForTenant((int) $request->tenant_id, (int) $request->id);
            if ($locked === null) {
                abort(404);
            }
            if (! in_array($locked->status, [
                LeaveRequestStatus::PENDING,
                LeaveRequestStatus::MANAGER_APPROVED,
                LeaveRequestStatus::HR_APPROVED,
            ], true)) {
                return $locked;
            }
            if ($locked->status === LeaveRequestStatus::HR_APPROVED) {
                $locked->loadMissing('leaveType');
                $this->balances->restore($locked);
                $this->attendanceSync->revertSyncedLeaveAttendance($locked, (int) $approver->id);
            }
            $updated = $this->requests->update($locked, [
                'status' => LeaveRequestStatus::REJECTED,
                'rejected_by' => $approver->id,
                'rejected_at' => now(),
                'approved_by' => $approver->id,
                'approved_at' => now(),
            ]);
            DB::afterCommit(function () use ($updated, $approver): void {
                $this->events->dispatch(new LeaveRequestRejected($updated, $approver->id));
            });

            return $updated;
        });
    }
}
