<?php

declare(strict_types=1);

namespace App\Support\Rbac;

use App\Models\User;
use App\Models\Workspace;
use App\Services\Auth\WorkspaceCapabilityResolver;
use App\Support\Auth\Capabilities;
use App\Support\Auth\UiTooltips as AuthUiTooltips;
use Closure;
use Filament\Actions\Action;
use Illuminate\Database\Eloquent\Model;
use ReflectionObject;
use Throwable;

/**
 * Central workspace-scoped RBAC UI Enforcement Helper for Filament Actions.
 *
 * Mirrors the tenant-scoped UiEnforcement semantics, but uses WorkspaceMembership
 * + WorkspaceCapabilityResolver.
 *
 * Rules:
 * - Non-member → hidden UI + 404 server-side
 * - Member without capability → visible-but-disabled + tooltip + 403 server-side
 * - Member with capability → enabled
 */
final class WorkspaceUiEnforcement
{
    private Action $action;

    private bool $requireMembership = true;

    private ?string $capability = null;

    private bool $isDestructive = false;

    private ?string $customTooltip = null;

    private Model|Closure|null $record = null;

    private bool $preserveExistingVisibility = false;

    private bool $preserveExistingDisabled = false;

    private function __construct(Action $action)
    {
        $this->action = $action;
    }

    /**
     * Create enforcement for a table action.
     *
     * @param  Action  $action  The Filament action to wrap
     * @param  Model|Closure  $record  The owner record or a closure that returns it
     */
    public static function forTableAction(Action $action, Model|Closure $record): self
    {
        $instance = new self($action);
        $instance->record = $record;

        return $instance;
    }

    public static function forAction(Action $action, Model|Closure|null $record = null): self
    {
        $instance = new self($action);
        $instance->record = $record;

        return $instance;
    }

    public function requireMembership(bool $require = true): self
    {
        $this->requireMembership = $require;

        return $this;
    }

    /**
     * @throws \InvalidArgumentException If capability is not in the canonical registry
     */
    public function requireCapability(string $capability): self
    {
        if (! Capabilities::isKnown($capability)) {
            throw new \InvalidArgumentException(
                "Unknown capability: {$capability}. Use constants from ".Capabilities::class
            );
        }

        $this->capability = $capability;

        return $this;
    }

    public function destructive(): self
    {
        $this->isDestructive = true;

        return $this;
    }

    public function tooltip(string $message): self
    {
        $this->customTooltip = $message;

        return $this;
    }

    public function preserveVisibility(): self
    {
        $this->preserveExistingVisibility = true;

        return $this;
    }

    public function preserveDisabled(): self
    {
        $this->preserveExistingDisabled = true;

        return $this;
    }

    public function apply(): Action
    {
        $this->applyVisibility();
        $this->applyDisabledState();
        $this->applyDestructiveConfirmation();
        $this->applyServerSideGuard();

        return $this->action;
    }

    private function applyVisibility(): void
    {
        if (! $this->requireMembership) {
            return;
        }

        $existingVisibility = $this->preserveExistingVisibility
            ? $this->getExistingVisibilityCondition()
            : null;

        $this->action->visible(function (?Model $record = null) use ($existingVisibility): bool {
            $context = $this->resolveContextWithRecord($record);

            if (! $context->isMember) {
                return false;
            }

            if ($existingVisibility === null) {
                return true;
            }

            return $this->evaluateVisibilityCondition($existingVisibility, $record);
        });
    }

    private function applyDisabledState(): void
    {
        if ($this->capability === null) {
            return;
        }

        $tooltip = $this->customTooltip ?? AuthUiTooltips::insufficientPermission();

        $existingDisabled = $this->preserveExistingDisabled
            ? $this->getExistingDisabledCondition()
            : null;

        $this->action->disabled(function (?Model $record = null) use ($existingDisabled): bool {
            if ($existingDisabled !== null && $this->evaluateDisabledCondition($existingDisabled, $record)) {
                return true;
            }

            $context = $this->resolveContextWithRecord($record);

            if (! $context->isMember) {
                return true;
            }

            return ! $context->hasCapability;
        });

        $this->action->tooltip(function (?Model $record = null) use ($tooltip): ?string {
            $context = $this->resolveContextWithRecord($record);

            if ($context->isMember && ! $context->hasCapability) {
                return $tooltip;
            }

            return null;
        });
    }

    private function applyDestructiveConfirmation(): void
    {
        if (! $this->isDestructive) {
            return;
        }

        $this->action->requiresConfirmation();
        $this->action->modalHeading(UiTooltips::DESTRUCTIVE_CONFIRM_TITLE);
        $this->action->modalDescription(UiTooltips::DESTRUCTIVE_CONFIRM_DESCRIPTION);
    }

    private function applyServerSideGuard(): void
    {
        $this->action->before(function (?Model $record = null): void {
            $context = $this->resolveContextWithRecord($record);

            if ($context->shouldDenyAsNotFound()) {
                abort(404);
            }

            if ($context->shouldDenyAsForbidden()) {
                abort(403);
            }
        });
    }

    private function getExistingVisibilityCondition(): bool|Closure|null
    {
        try {
            $ref = new ReflectionObject($this->action);

            if (! $ref->hasProperty('isVisible')) {
                return null;
            }

            $property = $ref->getProperty('isVisible');
            $property->setAccessible(true);

            /** @var bool|Closure $value */
            $value = $property->getValue($this->action);

            return $value;
        } catch (Throwable) {
            return null;
        }
    }

    private function evaluateVisibilityCondition(bool|Closure $condition, ?Model $record): bool
    {
        if (is_bool($condition)) {
            return $condition;
        }

        try {
            $reflection = new \ReflectionFunction($condition);
            $parameters = $reflection->getParameters();

            if ($parameters === []) {
                return (bool) $condition();
            }

            if ($record === null) {
                return false;
            }

            return (bool) $condition($record);
        } catch (Throwable) {
            return false;
        }
    }

    private function getExistingDisabledCondition(): bool|Closure|null
    {
        try {
            $ref = new ReflectionObject($this->action);

            if (! $ref->hasProperty('isDisabled')) {
                return null;
            }

            $property = $ref->getProperty('isDisabled');
            $property->setAccessible(true);

            /** @var bool|Closure $value */
            $value = $property->getValue($this->action);

            return $value;
        } catch (Throwable) {
            return null;
        }
    }

    private function evaluateDisabledCondition(bool|Closure $condition, ?Model $record): bool
    {
        if (is_bool($condition)) {
            return $condition;
        }

        try {
            $reflection = new \ReflectionFunction($condition);
            $parameters = $reflection->getParameters();

            if ($parameters === []) {
                return (bool) $condition();
            }

            if ($record === null) {
                return true;
            }

            return (bool) $condition($record);
        } catch (Throwable) {
            return true;
        }
    }

    private function resolveContextWithRecord(?Model $record = null): WorkspaceAccessContext
    {
        $user = auth()->user();
        $workspace = $this->resolveWorkspaceWithRecord($record);

        if (! $user instanceof User || ! $workspace instanceof Workspace) {
            return new WorkspaceAccessContext(
                user: null,
                workspace: null,
                isMember: false,
                hasCapability: false,
            );
        }

        /** @var WorkspaceCapabilityResolver $resolver */
        $resolver = app(WorkspaceCapabilityResolver::class);

        $isMember = $resolver->isMember($user, $workspace);

        $hasCapability = true;
        if ($this->capability !== null && $isMember) {
            $hasCapability = $resolver->can($user, $workspace, $this->capability);
        }

        return new WorkspaceAccessContext(
            user: $user,
            workspace: $workspace,
            isMember: $isMember,
            hasCapability: $hasCapability,
        );
    }

    private function resolveWorkspaceWithRecord(?Model $record = null): ?Workspace
    {
        if ($record instanceof Workspace) {
            return $record;
        }

        if ($this->record !== null) {
            try {
                $resolved = $this->record instanceof Closure
                    ? ($this->record)()
                    : $this->record;

                if ($resolved instanceof Workspace) {
                    return $resolved;
                }
            } catch (Throwable) {
                return null;
            }
        }

        return null;
    }
}
