<?php

namespace App\Services;

use App\Models\User;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Collection;

class HrmsNotificationService
{
    /**
     * Notify each manager in the reporting chain (direct manager first, walking up).
     *
     * @return list<int>
     */
    public function notifyManagerChain(User $requester, Notification $notification, array $excludeIds = []): array
    {
        $notified = [];
        $cursorId = $requester->report_to;

        while ($cursorId) {
            if (in_array((int) $cursorId, $notified, true) || in_array((int) $cursorId, $excludeIds, true)) {
                break;
            }

            $manager = User::withoutGlobalScopes()->find($cursorId);
            if (! $manager) {
                break;
            }

            $manager->notify($notification);
            $notified[] = (int) $manager->id;
            $cursorId = $manager->report_to;
        }

        return $notified;
    }

    /**
     * @param  list<string>  $permissions  User needs any one of these permissions.
     * @return Collection<int, User>
     */
    public function usersWithAnyPermission(int $companyId, array $permissions, array $excludeIds = []): Collection
    {
        if ($companyId <= 0 || $permissions === []) {
            return collect();
        }

        return User::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('is_superadmin', false)
            ->when($excludeIds !== [], fn ($q) => $q->whereNotIn('id', $excludeIds))
            ->whereHas('role.permissions', fn ($q) => $q->whereIn('name', $permissions))
            ->get();
    }

    /**
     * @return list<int>
     */
    public function notifyUsersWithAnyPermission(int $companyId, array $permissions, Notification $notification, array $excludeIds = []): array
    {
        $notified = [];
        foreach ($this->usersWithAnyPermission($companyId, $permissions, $excludeIds) as $user) {
            $user->notify($notification);
            $notified[] = (int) $user->id;
        }

        return $notified;
    }

    /**
     * Notify the employee's direct manager only; if none, fall back to attendances_edit holders.
     *
     * @return list<int>
     */
    public function notifyRegularizationApprovers(User $requester, Notification $notification): array
    {
        $exclude = [(int) $requester->id];

        if ($requester->report_to) {
            $manager = User::withoutGlobalScopes()->find($requester->report_to);
            if ($manager) {
                $manager->notify($notification);

                return [(int) $manager->id];
            }
        }

        if ($requester->company_id) {
            return $this->notifyUsersWithAnyPermission(
                (int) $requester->company_id,
                ['attendances_approve_regularization', 'attendances_edit'],
                $notification,
                $exclude
            );
        }

        return [];
    }

    /**
     * Notify manager chain; if empty, fall back to HR approvers.
     *
     * @return list<int>
     */
    public function notifyLeaveApprovers(User $requester, Notification $notification): array
    {
        $notified = $this->notifyManagerChain($requester, $notification);

        if ($notified === [] && $requester->company_id) {
            $notified = $this->notifyUsersWithAnyPermission(
                (int) $requester->company_id,
                ['leaves_approve_reject'],
                $notification,
                [(int) $requester->id]
            );
        }

        return $notified;
    }

    /**
     * @return list<int>
     */
    public function notifyResignationApprovers(User $employee, Notification $notification): array
    {
        $exclude = [(int) $employee->id];
        $notified = $this->notifyManagerChain($employee, $notification, $exclude);

        if ($employee->company_id) {
            $hrNotified = $this->notifyUsersWithAnyPermission(
                (int) $employee->company_id,
                ['hr_operations_view'],
                $notification,
                array_merge($exclude, $notified)
            );
            $notified = array_values(array_unique(array_merge($notified, $hrNotified)));
        }

        return $notified;
    }

    /**
     * @return list<int>
     */
    public function notifyFnfApprovers(int $companyId, Notification $notification, array $excludeIds = []): array
    {
        return $this->notifyUsersWithAnyPermission(
            $companyId,
            ['hr_operations_view', 'finance_view', 'finance_operational', 'finance_edit'],
            $notification,
            $excludeIds
        );
    }

    /**
     * @return list<int>
     */
    public function notifyExpenseApprovers(int $companyId, Notification $notification, array $excludeIds = []): array
    {
        return $this->notifyUsersWithAnyPermission(
            $companyId,
            ['finance_edit', 'expenses_approve', 'finance_view', 'finance_treasury_view', 'finance_operational', 'finance_team_view'],
            $notification,
            $excludeIds
        );
    }

    /**
     * @return list<int>
     */
    public function notifyIncomeTaxVerifiers(int $companyId, Notification $notification, array $excludeIds = []): array
    {
        return $this->notifyUsersWithAnyPermission(
            $companyId,
            ['payrolls_view', 'payrolls_edit'],
            $notification,
            $excludeIds
        );
    }

    /**
     * @return list<int>
     */
    public function notifyOffboardingStakeholders(int $companyId, Notification $notification, array $excludeIds = []): array
    {
        return $this->notifyUsersWithAnyPermission(
            $companyId,
            ['hr_operations_view', 'finance_view', 'finance_operational'],
            $notification,
            $excludeIds
        );
    }

    /**
     * @return list<int>
     */
    public function notifyActiveCompanyUsers(int $companyId, Notification $notification, array $excludeIds = []): array
    {
        $notified = [];
        User::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('is_superadmin', false)
            ->when($excludeIds !== [], fn ($q) => $q->whereNotIn('id', $excludeIds))
            ->where('status', User::STATUS_ACTIVE)
            ->chunkById(100, function ($users) use ($notification, &$notified) {
                foreach ($users as $user) {
                    $user->notify($notification);
                    $notified[] = (int) $user->id;
                }
            });

        return $notified;
    }
}
