<?php

namespace App\Policies;

use App\Models\ProviderConnection;
use App\Models\Tenant;
use App\Models\TenantMembership;
use App\Models\User;
use App\Models\Workspace;
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();
        }

        $entitledTenants = Tenant::query()
            ->select('tenants.*')
            ->join('tenant_memberships as policy_memberships', function ($join) use ($user): void {
                $join->on('policy_memberships.tenant_id', '=', 'tenants.id')
                    ->where('policy_memberships.user_id', '=', (int) $user->getKey());
            })
            ->where('tenants.workspace_id', (int) $workspace->getKey())
            ->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 Tenant || (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->tenant_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 Tenant || ! $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 Tenant || (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->tenant_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 Tenant || (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->tenant_id !== (int) $tenant->getKey()) {
            return Response::denyAsNotFound();
        }

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

        return true;
    }

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

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

            if ($filamentTenant instanceof Tenant) {
                $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): ?Tenant
    {
        $tenantExternalId = request()->query('tenant_id');

        if (! is_string($tenantExternalId) || $tenantExternalId === '') {
            $lastTenantId = app(WorkspaceContext::class)->lastTenantId(request());

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

            $filamentTenant = Filament::getTenant();

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

            return null;
        }

        return Tenant::query()
            ->where('external_id', $tenantExternalId)
            ->where('workspace_id', (int) $workspace->getKey())
            ->first();
    }

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

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

        return null;
    }

    private function isTenantMember(User $user, Tenant $tenant): bool
    {
        return TenantMembership::query()
            ->where('user_id', (int) $user->getKey())
            ->where('tenant_id', (int) $tenant->getKey())
            ->exists();
    }
}
