<?php

namespace App\Services\Auth;

use App\Models\User;
use App\Models\Workspace;
use App\Models\WorkspaceMembership;
use App\Support\Auth\Capabilities;
use App\Support\Auth\WorkspaceRole;
use Illuminate\Support\Facades\Log;

/**
 * Workspace Capability Resolver
 *
 * Resolves user memberships and capabilities for a given workspace.
 * Caches results per request to avoid N+1 queries.
 */
class WorkspaceCapabilityResolver
{
    private array $resolvedMemberships = [];

    private array $loggedDenials = [];

    public function getRole(User $user, Workspace $workspace): ?WorkspaceRole
    {
        $membership = $this->getMembership($user, $workspace);

        if ($membership === null) {
            return null;
        }

        return WorkspaceRole::tryFrom($membership['role']);
    }

    public function can(User $user, Workspace $workspace, string $capability): bool
    {
        if (! Capabilities::isKnown($capability)) {
            throw new \InvalidArgumentException("Unknown capability: {$capability}");
        }

        $role = $this->getRole($user, $workspace);

        if ($role === null) {
            $this->logDenial($user, $workspace, $capability);

            return false;
        }

        $allowed = WorkspaceRoleCapabilityMap::hasCapability($role, $capability);

        if (! $allowed) {
            $this->logDenial($user, $workspace, $capability);
        }

        return $allowed;
    }

    public function isMember(User $user, Workspace $workspace): bool
    {
        return $this->getMembership($user, $workspace) !== null;
    }

    public function clearCache(): void
    {
        $this->resolvedMemberships = [];
    }

    /**
     * Prime workspace membership cache for a set of workspaces in one query.
     *
     * @param  array<int, int|string>  $workspaceIds
     */
    public function primeMemberships(User $user, array $workspaceIds): void
    {
        $workspaceIds = array_values(array_unique(array_map(static fn ($id): int => (int) $id, $workspaceIds)));

        if ($workspaceIds === []) {
            return;
        }

        $memberships = WorkspaceMembership::query()
            ->where('user_id', (int) $user->getKey())
            ->whereIn('workspace_id', $workspaceIds)
            ->get(['workspace_id', 'role']);

        $byWorkspaceId = $memberships->keyBy('workspace_id');

        foreach ($workspaceIds as $workspaceId) {
            $cacheKey = "workspace_membership_{$user->id}_{$workspaceId}";
            $membership = $byWorkspaceId->get($workspaceId);
            $this->resolvedMemberships[$cacheKey] = $membership?->toArray();
        }
    }

    private function logDenial(User $user, Workspace $workspace, string $capability): void
    {
        $key = implode(':', [(string) $user->getKey(), (string) $workspace->getKey(), $capability]);

        if (isset($this->loggedDenials[$key])) {
            return;
        }

        $this->loggedDenials[$key] = true;

        Log::warning('rbac.workspace.denied', [
            'capability' => $capability,
            'workspace_id' => (int) $workspace->getKey(),
            'actor_user_id' => (int) $user->getKey(),
        ]);
    }

    private function getMembership(User $user, Workspace $workspace): ?array
    {
        $cacheKey = "workspace_membership_{$user->id}_{$workspace->id}";

        if (! array_key_exists($cacheKey, $this->resolvedMemberships)) {
            $membership = WorkspaceMembership::query()
                ->where('user_id', $user->id)
                ->where('workspace_id', $workspace->id)
                ->first(['role']);

            $this->resolvedMemberships[$cacheKey] = $membership?->toArray();
        }

        return $this->resolvedMemberships[$cacheKey];
    }
}
