<?php

namespace App\Http\Middleware;

use App\Models\Branch;
use App\Models\User;
use App\Services\BranchAccessService;
use Closure;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureBranchAccess
{
    public function __construct(private BranchAccessService $branchAccess)
    {
    }

    public function handle(Request $request, Closure $next, ?string $parameter = null): Response
    {
        $user = $request->user();

        if (! $user) {
            return $next($request);
        }

        if ($parameter !== null) {
            $value = $request->route($parameter);

            if ($value instanceof Model) {
                $this->assertModelAccess($user, $value);
            } elseif (is_numeric($value)) {
                $this->branchAccess->assertBranchAccessible($user, (int) $value);
            }
        } else {
            foreach ($request->route()?->parameters() ?? [] as $value) {
                if ($value instanceof Model) {
                    $this->assertModelAccess($user, $value);
                }
            }

            foreach ($this->submittedBranchIds($request->all()) as $branchId) {
                $this->branchAccess->assertBranchAccessible($user, $branchId);
            }
        }

        return $next($request);
    }

    private function assertModelAccess(User $user, Model $model): void
    {
        if ($model instanceof Branch) {
            $this->branchAccess->assertBranchAccessible($user, (int) $model->getKey());
            return;
        }

        if ($model instanceof User) {
            abort_unless(
                $this->branchAccess->sharesAnyBranch($user, $model),
                403,
                'This staff record belongs to a branch outside your assigned scope.'
            );
            return;
        }

        $branchId = $model->getAttribute('branch_id');
        if ($branchId !== null) {
            $this->branchAccess->assertBranchAccessible($user, (int) $branchId);
        }
    }

    /** @return array<int> */
    private function submittedBranchIds(array $payload): array
    {
        $branchIds = [];

        $walk = function (array $values) use (&$walk, &$branchIds): void {
            foreach ($values as $key => $value) {
                $key = is_string($key) ? $key : '';

                if ($key !== '' && preg_match('/(^|_)branch_ids$/', $key) === 1 && is_array($value)) {
                    foreach ($value as $candidate) {
                        if (is_scalar($candidate) && ctype_digit((string) $candidate) && (int) $candidate > 0) {
                            $branchIds[] = (int) $candidate;
                        }
                    }
                    continue;
                }

                if ($key !== '' && preg_match('/(^|_)branch_id$/', $key) === 1 && is_scalar($value)) {
                    if (ctype_digit((string) $value) && (int) $value > 0) {
                        $branchIds[] = (int) $value;
                    }
                    continue;
                }

                if (is_array($value)) {
                    $walk($value);
                }
            }
        };

        $walk($payload);

        return array_values(array_unique($branchIds));
    }
}
