<?php

declare(strict_types=1);

namespace App\Http\Middleware;

use App\Models\User;
use App\Models\Workspace;
use App\Models\WorkspaceMembership;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Support\Audit\AuditActionId;
use App\Support\Navigation\WorkspaceHubRegistry;
use App\Support\Workspaces\WorkspaceContext;
use App\Support\Workspaces\WorkspaceIntendedUrl;
use App\Support\Workspaces\WorkspaceRedirectResolver;
use Closure;
use Filament\Notifications\Notification;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Symfony\Component\HttpFoundation\Response;

class EnsureWorkspaceSelected
{
    /**
     * Handle an incoming request.
     *
     * Spec 107 — 7-step algorithm:
     *  1. If workspace-optional path → allow
     *  2. If ?choose=1 → redirect to chooser
     *  3. If session set → validate membership; stale → clear + warn + chooser
     *  4. Load selectable memberships
     *  5. If exactly 1 → auto-select + audit + redirect via tenant branching
     *  6. If last_workspace_id valid → auto-select + audit + redirect
     *  7. Else → redirect to chooser
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        // Auth-related routes are always allowed.
        $routeName = $request->route()?->getName();

        if (is_string($routeName) && str_contains($routeName, '.auth.')) {
            return $next($request);
        }

        $path = '/'.ltrim($request->path(), '/');

        // --- Step 1: workspace-optional bypass ---
        if ($this->isWorkspaceOptionalPath($request, $path)) {
            return $next($request);
        }

        $user = $request->user();

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

        /** @var WorkspaceContext $context */
        $context = app(WorkspaceContext::class);

        // --- Step 2: forced chooser via ?choose=1 ---
        if ($request->query('choose') === '1') {
            return $this->redirectToChooser();
        }

        // --- Step 3: validate active session ---
        $currentId = $context->currentWorkspaceId($request);

        if ($currentId !== null) {
            $workspace = Workspace::query()->whereKey($currentId)->first();

            if (
                $workspace instanceof Workspace
                && $workspace->isSelectableAsContext()
                && $context->isMember($user, $workspace)
            ) {
                return $next($request);
            }

            $this->clearStaleSession($context, $user, $request, $workspace);

            if ($workspace instanceof Workspace && $workspace->isSelectableAsContext()) {
                abort(404);
            }

            return $this->redirectToChooser();
        }

        // --- Step 4: load selectable workspace memberships ---
        $selectableMemberships = WorkspaceMembership::query()
            ->where('user_id', $user->getKey())
            ->join('workspaces', 'workspace_memberships.workspace_id', '=', 'workspaces.id')
            ->whereNull('workspaces.archived_at')
            ->whereNull('workspaces.closed_at')
            ->select('workspace_memberships.*')
            ->get();

        if ($this->isChooserFirstPath($path)) {
            return $this->redirectToWorkspaceSelection($request, $user, $selectableMemberships);
        }

        // --- Step 5: single membership auto-resume ---
        if ($selectableMemberships->count() === 1) {
            /** @var WorkspaceMembership $membership */
            $membership = $selectableMemberships->first();
            $workspace = Workspace::query()->whereKey($membership->workspace_id)->first();

            if ($workspace instanceof Workspace) {
                $context->setCurrentWorkspace($workspace, $user, $request);

                $this->emitAuditEvent(
                    workspace: $workspace,
                    user: $user,
                    actionId: AuditActionId::WorkspaceAutoSelected,
                    method: 'auto',
                    reason: 'single_membership',
                );

                if ($this->requestHasExplicitTenantContext($request)) {
                    return $next($request);
                }

                return $this->redirectViaTenantBranching($workspace, $user);
            }
        }

        // --- Step 6: last_workspace_id auto-resume ---
        if ($user->last_workspace_id !== null) {
            $lastWorkspace = Workspace::query()->whereKey($user->last_workspace_id)->first();

            if (
                $lastWorkspace instanceof Workspace
                && $lastWorkspace->isSelectableAsContext()
                && $context->isMember($user, $lastWorkspace)
            ) {
                $context->setCurrentWorkspace($lastWorkspace, $user, $request);

                $this->emitAuditEvent(
                    workspace: $lastWorkspace,
                    user: $user,
                    actionId: AuditActionId::WorkspaceAutoSelected,
                    method: 'auto',
                    reason: 'last_used',
                );

                if ($this->requestHasExplicitTenantContext($request)) {
                    return $next($request);
                }

                return $this->redirectViaTenantBranching($lastWorkspace, $user);
            }

            // Stale last_workspace_id — clear and warn.
            $workspaceName = $lastWorkspace?->name;
            $user->forceFill(['last_workspace_id' => null])->save();

            if ($workspaceName !== null) {
                $message = $lastWorkspace?->isClosed()
                    ? "The workspace {$workspaceName} was closed."
                    : "Your access to {$workspaceName} was removed.";

                Notification::make()
                    ->title($message)
                    ->danger()
                    ->send();
            }
        }

        // --- Step 7: fallback to chooser ---
        return $this->redirectToWorkspaceSelection($request, $user, $selectableMemberships);
    }

    private function isWorkspaceOptionalPath(Request $request, string $path): bool
    {
        if (str_starts_with($path, '/admin/workspaces')) {
            return true;
        }

        if (in_array($path, ['/admin/choose-workspace', '/admin/no-access', '/admin/onboarding', '/admin/settings/workspace'], true)) {
            return true;
        }

        if (preg_match('#^/admin/onboarding/[^/]+$#', $path) === 1) {
            return true;
        }

        if ($this->isLivewireUpdatePath($path)) {
            $refererPath = parse_url((string) $request->headers->get('referer', ''), PHP_URL_PATH) ?? '';
            $refererPath = '/'.ltrim((string) $refererPath, '/');

            if (preg_match('#^/admin/onboarding(?:/[^/]+)?$#', $refererPath) === 1) {
                return true;
            }
        }

        return false;
    }

    private function isLivewireUpdatePath(string $path): bool
    {
        return preg_match('#^/livewire(?:-[^/]+)?/update$#', $path) === 1;
    }

    private function isChooserFirstPath(string $path): bool
    {
        return in_array($path, ['/admin', '/admin/choose-environment'], true);
    }

    private function requestHasExplicitTenantContext(Request $request): bool
    {
        if (WorkspaceHubRegistry::isWorkspaceHubPath('/'.ltrim((string) $request->path(), '/'))) {
            return false;
        }

        if (filled($request->query('tenant')) || filled($request->query('managed_environment_id'))) {
            return true;
        }

        $route = $request->route();

        return $route?->hasParameter('tenant') && filled($route->parameter('tenant'));
    }

    private function redirectToChooser(): Response
    {
        return new \Illuminate\Http\Response('', 302, ['Location' => '/admin/choose-workspace']);
    }

    /**
     * @param  Collection<int, WorkspaceMembership>  $selectableMemberships
     */
    private function redirectToWorkspaceSelection(Request $request, User $user, Collection $selectableMemberships): Response
    {
        if ($selectableMemberships->isNotEmpty()) {
            WorkspaceIntendedUrl::storeFromRequest($request);
        }

        $canCreate = $user->can('create', Workspace::class);
        $target = ($selectableMemberships->isNotEmpty() || $canCreate)
            ? '/admin/choose-workspace'
            : '/admin/no-access';

        return new \Illuminate\Http\Response('', 302, ['Location' => $target]);
    }

    private function redirectViaTenantBranching(Workspace $workspace, User $user): Response
    {
        /** @var WorkspaceRedirectResolver $resolver */
        $resolver = app(WorkspaceRedirectResolver::class);

        $url = $resolver->resolve($workspace, $user);

        return new \Illuminate\Http\Response('', 302, ['Location' => $url]);
    }

    private function clearStaleSession(WorkspaceContext $context, User $user, Request $request, ?Workspace $workspace): void
    {
        $workspaceName = $workspace?->name;

        $session = $request->hasSession() ? $request->session() : session();
        $session->forget(WorkspaceContext::SESSION_KEY);

        if ($user->last_workspace_id !== null && $context->currentWorkspaceId($request) === null) {
            $user->forceFill(['last_workspace_id' => null])->save();
        }

        if ($workspaceName !== null) {
            $message = $workspace?->isClosed()
                ? "The workspace {$workspaceName} was closed."
                : "Your access to {$workspaceName} was removed.";

            Notification::make()
                ->title($message)
                ->danger()
                ->send();
        }
    }

    private function emitAuditEvent(
        Workspace $workspace,
        User $user,
        AuditActionId $actionId,
        string $method,
        string $reason,
        ?int $prevWorkspaceId = null,
    ): void {
        /** @var WorkspaceAuditLogger $logger */
        $logger = app(WorkspaceAuditLogger::class);

        $logger->log(
            workspace: $workspace,
            action: $actionId->value,
            context: [
                'metadata' => [
                    'method' => $method,
                    'reason' => $reason,
                    'prev_workspace_id' => $prevWorkspaceId,
                ],
            ],
            actor: $user,
            resourceType: 'workspace',
            resourceId: (string) $workspace->getKey(),
        );
    }
}
