<?php

namespace App\Services;

use App\Models\Branch;
use App\Models\User;
use App\Support\Authorization\PermissionName;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;

class BranchAccessService
{
    public function isSystemWide(User $user): bool
    {
        return $user->can(PermissionName::ROLES_ASSIGN_SUPER_ADMIN->value);
    }

    /** @return array<int> */
    public function assignedBranchIds(User $user): array
    {
        return collect([$user->primary_branch_id])
            ->merge($user->branches()->pluck('branches.id'))
            ->filter(fn ($id) => (int) $id > 0)
            ->map(fn ($id) => (int) $id)
            ->unique()
            ->values()
            ->all();
    }

    /** @return array<int> */
    public function accessibleBranchIds(User $user): array
    {
        if ($this->isSystemWide($user)) {
            return Branch::query()
                ->pluck('id')
                ->map(fn ($id) => (int) $id)
                ->all();
        }

        return $this->assignedBranchIds($user);
    }

    public function accessibleBranches(User $user): Collection
    {
        return Branch::query()
            ->whereIn('id', $this->accessibleBranchIds($user))
            ->orderBy('name')
            ->get();
    }

    public function canAccessBranch(User $user, int $branchId): bool
    {
        return $branchId > 0
            && in_array($branchId, $this->accessibleBranchIds($user), true);
    }

    public function isAssignedToBranch(User $user, int $branchId): bool
    {
        return $branchId > 0
            && in_array($branchId, $this->assignedBranchIds($user), true);
    }

    public function assertBranchAccessible(User $user, int $branchId): void
    {
        abort_unless(
            $this->canAccessBranch($user, $branchId),
            403,
            'This record belongs to a branch outside your assigned scope.'
        );
    }

    /** @param array<int|string> $branchIds */
    public function assertBranchesAccessible(User $user, array $branchIds): void
    {
        $requested = collect($branchIds)
            ->map(fn ($id) => (int) $id)
            ->filter(fn ($id) => $id > 0)
            ->unique()
            ->values()
            ->all();

        abort_unless(
            empty(array_diff($requested, $this->accessibleBranchIds($user))),
            403,
            'One or more selected branches are outside your assigned scope.'
        );
    }

    public function constrainBranchOwnedQuery(Builder $query, User $user, string $column = 'branch_id'): Builder
    {
        if ($this->isSystemWide($user)) {
            return $query;
        }

        $branchIds = $this->accessibleBranchIds($user);

        return $branchIds === []
            ? $query->whereRaw('1 = 0')
            : $query->whereIn($column, $branchIds);
    }

    public function sharesAnyBranch(User $actor, User $target): bool
    {
        if ($this->isSystemWide($actor)) {
            return true;
        }

        return count(array_intersect(
            $this->accessibleBranchIds($actor),
            $this->assignedBranchIds($target)
        )) > 0;
    }
}
