<?php

namespace App\Policies;

use App\Models\ManagedEnvironment;
use App\Models\ProviderConnection;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Auth\ManagedEnvironmentAccessScopeResolver;
use App\Support\Auth\Capabilities;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Facades\Filament;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate;

class ProviderConnectionPolicy
{
    use HandlesAuthorization;

    public function viewAny(User $user): Response|bool
    {
        $workspace = $this->currentWorkspace($user);

        if (! $workspace instanceof Workspace) {
            return Response::denyAsNotFound();
        }

        /** @var ManagedEnvironmentAccessScopeResolver $scopeResolver */
        $scopeResolver = app(ManagedEnvironmentAccessScopeResolver::class);

        $entitledTenantsQuery = ManagedEnvironment::query()
            ->where('managed_environments.workspace_id', (int) $workspace->getKey());

        $scopeResolver->applyWorkspaceScopeToQuery(
            query: $entitledTenantsQuery,
            user: $user,
            workspaceId: (int) $workspace->getKey(),
            qualifiedEnvironmentColumn: 'managed_environments.id',
        );

        $entitledTenants = $entitledTenantsQuery->get();

        if ($entitledTenants->isEmpty()) {
            return true;
        }

        foreach ($entitledTenants as $tenant) {
            if (Gate::forUser($user)->allows(Capabilities::PROVIDER_VIEW, $tenant)) {
                return true;
            }
        }

        return false;
    }

    public function view(User $user, ProviderConnection $connection): Response|bool
    {
        $workspace = $this->currentWorkspace($user);

        if (! $workspace instanceof Workspace) {
            return Response::denyAsNotFound();
        }

        $tenant = $this->tenantForConnection($connection);

        if (! $tenant instanceof ManagedEnvironment || (int) $tenant->workspace_id !== (int) $workspace->getKey()) {
            return Response::denyAsNotFound();
        }

        if (! $this->isTenantMember($user, $tenant)) {
            return Response::denyAsNotFound();
        }

        if (! Gate::forUser($user)->allows(Capabilities::PROVIDER_VIEW, $tenant)) {
            return false;
        }

        if ((int) $connection->managed_environment_id !== (int) $tenant->getKey()) {
            return Response::denyAsNotFound();
        }

        if ((int) $connection->workspace_id !== (int) $workspace->getKey()) {
            return Response::denyAsNotFound();
        }

        return true;
    }

    public function create(User $user): Response|bool
    {
        $workspace = $this->currentWorkspace($user);

        if (! $workspace instanceof Workspace) {
            return Response::denyAsNotFound();
        }

        $tenant = $this->resolveCreateTenant($workspace);

        if (! $tenant instanceof ManagedEnvironment || ! $this->isTenantMember($user, $tenant)) {
            return Response::denyAsNotFound();
        }

        if (! Gate::forUser($user)->allows(Capabilities::PROVIDER_MANAGE, $tenant)) {
            return false;
        }

        return true;
    }

    public function update(User $user, ProviderConnection $connection): Response|bool
    {
        $workspace = $this->currentWorkspace($user);

        if (! $workspace instanceof Workspace) {
            return Response::denyAsNotFound();
        }

        $tenant = $this->tenantForConnection($connection);

        if (! $tenant instanceof ManagedEnvironment || (int) $tenant->workspace_id !== (int) $workspace->getKey()) {
            return Response::denyAsNotFound();
        }

        if (! $this->isTenantMember($user, $tenant)) {
            return Response::denyAsNotFound();
        }

        if (! Gate::forUser($user)->allows(Capabilities::PROVIDER_MANAGE, $tenant)) {
            return false;
        }

        if ((int) $connection->managed_environment_id !== (int) $tenant->getKey()) {
            return Response::denyAsNotFound();
        }

        if ((int) $connection->workspace_id !== (int) $workspace->getKey()) {
            return Response::denyAsNotFound();
        }

        return true;
    }

    public function delete(User $user, ProviderConnection $connection): Response|bool
    {
        $workspace = $this->currentWorkspace($user);

        if (! $workspace instanceof Workspace) {
            return Response::denyAsNotFound();
        }

        $tenant = $this->tenantForConnection($connection);

        if (! $tenant instanceof ManagedEnvironment || (int) $tenant->workspace_id !== (int) $workspace->getKey()) {
            return Response::denyAsNotFound();
        }

        if (! $this->isTenantMember($user, $tenant)) {
            return Response::denyAsNotFound();
        }

        if (! Gate::forUser($user)->allows(Capabilities::PROVIDER_MANAGE, $tenant)) {
            return false;
        }

        if ((int) $connection->managed_environment_id !== (int) $tenant->getKey()) {
            return Response::denyAsNotFound();
        }

        if ((int) $connection->workspace_id !== (int) $workspace->getKey()) {
            return Response::denyAsNotFound();
        }

        return true;
    }

    public function manageDedicated(User $user, ProviderConnection $connection): Response|bool
    {
        $baseAccess = $this->update($user, $connection);

        if ($baseAccess !== true) {
            return $baseAccess;
        }

        $tenant = $this->tenantForConnection($connection);

        if (! $tenant instanceof ManagedEnvironment) {
            return Response::denyAsNotFound();
        }

        return Gate::forUser($user)->allows(Capabilities::PROVIDER_MANAGE_DEDICATED, $tenant);
    }

    public function changeConnectionType(User $user, ProviderConnection $connection): Response|bool
    {
        return $this->manageDedicated($user, $connection);
    }

    public function manageDedicatedCredential(User $user, ProviderConnection $connection): Response|bool
    {
        return $this->manageDedicated($user, $connection);
    }

    public function deleteDedicatedCredential(User $user, ProviderConnection $connection): Response|bool
    {
        return $this->manageDedicated($user, $connection);
    }

    private function currentWorkspace(User $user): ?Workspace
    {
        $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request());

        if (! is_int($workspaceId)) {
            $filamentTenant = Filament::getTenant();

            if ($filamentTenant instanceof ManagedEnvironment) {
                $workspaceId = (int) $filamentTenant->workspace_id;
            }
        }

        if (! is_int($workspaceId)) {
            return null;
        }

        $workspace = Workspace::query()->whereKey($workspaceId)->first();

        if (! $workspace instanceof Workspace) {
            return null;
        }

        if (! app(WorkspaceContext::class)->isMember($user, $workspace)) {
            return null;
        }

        return $workspace;
    }

    private function resolveCreateTenant(Workspace $workspace): ?ManagedEnvironment
    {
        $requestedEnvironmentId = request()->query('environment_id');

        if (! is_numeric($requestedEnvironmentId)) {
            $lastEnvironmentId = app(WorkspaceContext::class)->lastEnvironmentId(request());

            if (is_int($lastEnvironmentId)) {
                return ManagedEnvironment::query()
                    ->whereKey($lastEnvironmentId)
                    ->where('workspace_id', (int) $workspace->getKey())
                    ->first();
            }

            $filamentTenant = Filament::getTenant();

            if ($filamentTenant instanceof ManagedEnvironment && (int) $filamentTenant->workspace_id === (int) $workspace->getKey()) {
                return $filamentTenant;
            }

            return null;
        }

        return ManagedEnvironment::query()
            ->whereKey((int) $requestedEnvironmentId)
            ->where('workspace_id', (int) $workspace->getKey())
            ->first();
    }

    private function tenantForConnection(ProviderConnection $connection): ?ManagedEnvironment
    {
        if ($connection->relationLoaded('tenant') && $connection->tenant instanceof ManagedEnvironment) {
            return $connection->tenant;
        }

        if (is_int($connection->managed_environment_id) || is_numeric($connection->managed_environment_id)) {
            return ManagedEnvironment::query()->whereKey((int) $connection->managed_environment_id)->first();
        }

        return null;
    }

    private function isTenantMember(User $user, ManagedEnvironment $tenant): bool
    {
        return app(ManagedEnvironmentAccessScopeResolver::class)->canAccess($user, $tenant);
    }
}
