<?php

namespace App\Support\Workspaces;

use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Models\Workspace;
use App\Models\WorkspaceMembership;
use App\Services\Tenants\TenantOperabilityService;
use App\Support\Tenants\TenantInteractionLane;
use App\Support\Tenants\TenantOperabilityQuestion;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

final class WorkspaceContext
{
    public const SESSION_KEY = 'current_workspace_id';

    public const INTENDED_URL_SESSION_KEY = 'workspace_intended_url';

    public const LAST_ENVIRONMENT_IDS_SESSION_KEY = 'workspace_last_environment_ids';

    public function __construct(
        private WorkspaceResolver $resolver,
        private TenantOperabilityService $tenantOperabilityService,
    ) {}

    public function currentWorkspaceId(?Request $request = null): ?int
    {
        $session = ($request && $request->hasSession()) ? $request->session() : session();

        $id = $session->get(self::SESSION_KEY);

        return is_int($id) ? $id : (is_numeric($id) ? (int) $id : null);
    }

    public function currentWorkspace(?Request $request = null): ?Workspace
    {
        $id = $this->currentWorkspaceId($request);

        if (! $id) {
            return null;
        }

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

        if (! $workspace) {
            return null;
        }

        if (! $this->isWorkspaceSelectable($workspace)) {
            return null;
        }

        return $workspace;
    }

    public function currentWorkspaceOrEnvironmentWorkspace(?ManagedEnvironment $tenant = null, ?Request $request = null): ?Workspace
    {
        $workspace = $this->currentWorkspace($request);

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

        if (! $tenant instanceof ManagedEnvironment) {
            return null;
        }

        $workspace = $tenant->workspace()->first();

        if (! $workspace instanceof Workspace || ! $this->isWorkspaceSelectable($workspace)) {
            return null;
        }

        $user = $request?->user() instanceof User ? $request->user() : auth()->user();

        if (! $user instanceof User || ! $this->isMember($user, $workspace) || ! $this->userCanAccessTenant($tenant, $request)) {
            return null;
        }

        return $workspace;
    }

    public function setCurrentWorkspace(Workspace $workspace, ?User $user = null, ?Request $request = null): void
    {
        if (! $this->isWorkspaceSelectable($workspace)) {
            throw new NotFoundHttpException;
        }

        $session = ($request && $request->hasSession()) ? $request->session() : session();
        $session->put(self::SESSION_KEY, (int) $workspace->getKey());

        if ($user !== null) {
            $user->forceFill(['last_workspace_id' => (int) $workspace->getKey()])->save();
        }
    }

    public function rememberLastEnvironmentId(int $workspaceId, int $tenantId, ?Request $request = null): void
    {
        $session = ($request && $request->hasSession()) ? $request->session() : session();

        $map = $session->get(self::LAST_ENVIRONMENT_IDS_SESSION_KEY, []);
        $map = is_array($map) ? $map : [];

        $map[(string) $workspaceId] = $tenantId;

        $session->put(self::LAST_ENVIRONMENT_IDS_SESSION_KEY, $map);
    }

    public function rememberEnvironmentContext(ManagedEnvironment $tenant, ?Request $request = null): bool
    {
        $workspaceId = $this->currentWorkspaceId($request);

        if ($workspaceId === null || (int) $tenant->workspace_id !== $workspaceId) {
            return false;
        }

        $outcome = $this->tenantOperabilityService->outcomeFor(
            tenant: $tenant,
            actor: $request?->user() instanceof User ? $request->user() : auth()->user(),
            workspaceId: $workspaceId,
            lane: TenantInteractionLane::StandardActiveOperating,
            question: TenantOperabilityQuestion::RememberedContextValidity,
        );

        if (! $outcome->allowed) {
            $this->clearLastEnvironmentId($request);

            return false;
        }

        if (! $this->userCanAccessTenant($tenant, $request)) {
            $this->clearRememberedEnvironmentContext($request);

            return false;
        }

        $this->rememberLastEnvironmentId($workspaceId, (int) $tenant->getKey(), $request);

        return true;
    }

    public function lastEnvironmentId(?Request $request = null): ?int
    {
        $workspaceId = $this->currentWorkspaceId($request);

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

        $session = ($request && $request->hasSession()) ? $request->session() : session();

        $map = $session->get(self::LAST_ENVIRONMENT_IDS_SESSION_KEY, []);
        $map = is_array($map) ? $map : [];

        $id = $map[(string) $workspaceId] ?? null;

        return is_int($id) ? $id : (is_numeric($id) ? (int) $id : null);
    }

    public function clearLastEnvironmentId(?Request $request = null): void
    {
        $workspaceId = $this->currentWorkspaceId($request);

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

        $session = ($request && $request->hasSession()) ? $request->session() : session();

        $map = $session->get(self::LAST_ENVIRONMENT_IDS_SESSION_KEY, []);
        $map = is_array($map) ? $map : [];

        unset($map[(string) $workspaceId]);

        $session->put(self::LAST_ENVIRONMENT_IDS_SESSION_KEY, $map);
    }

    public function clearRememberedEnvironmentContext(?Request $request = null): void
    {
        $this->clearLastEnvironmentId($request);
    }

    public function rememberedEnvironment(?Request $request = null): ?ManagedEnvironment
    {
        $workspaceId = $this->currentWorkspaceId($request);

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

        $rememberedEnvironmentId = $this->lastEnvironmentId($request);

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

        $tenant = ManagedEnvironment::query()
            ->withTrashed()
            ->whereKey($rememberedEnvironmentId)
            ->first();

        if (! $tenant instanceof ManagedEnvironment) {
            $this->clearRememberedEnvironmentContext($request);

            return null;
        }

        if ((int) $tenant->workspace_id !== $workspaceId) {
            $this->clearRememberedEnvironmentContext($request);

            return null;
        }

        if (! $this->userCanAccessTenant($tenant, $request)) {
            $this->clearRememberedEnvironmentContext($request);

            return null;
        }

        $outcome = $this->tenantOperabilityService->outcomeFor(
            tenant: $tenant,
            actor: $request?->user() instanceof User ? $request->user() : auth()->user(),
            workspaceId: $workspaceId,
            lane: TenantInteractionLane::StandardActiveOperating,
            question: TenantOperabilityQuestion::RememberedContextValidity,
        );

        if (! $outcome->allowed) {
            $this->clearRememberedEnvironmentContext($request);

            return null;
        }

        return $tenant;
    }

    public function clearCurrentWorkspace(?User $user = null, ?Request $request = null): void
    {
        $session = ($request && $request->hasSession()) ? $request->session() : session();
        $session->forget(self::SESSION_KEY);

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

    public function resolveInitialWorkspaceFor(User $user, ?Request $request = null): ?Workspace
    {
        $session = ($request && $request->hasSession()) ? $request->session() : session();

        $currentId = $this->currentWorkspaceId($request);

        if ($currentId) {
            $current = Workspace::query()->whereKey($currentId)->first();

            if (! $current instanceof Workspace || ! $this->isWorkspaceSelectable($current) || ! $this->isMember($user, $current)) {
                $session->forget(self::SESSION_KEY);

                if ((int) $user->last_workspace_id === (int) $currentId) {
                    $user->forceFill(['last_workspace_id' => null])->save();
                }
            } else {
                return $current;
            }
        }

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

            if (! $workspace instanceof Workspace || ! $this->isWorkspaceSelectable($workspace) || ! $this->isMember($user, $workspace)) {
                $user->forceFill(['last_workspace_id' => null])->save();
            }
        }

        $memberships = WorkspaceMembership::query()
            ->where('user_id', $user->getKey())
            ->with('workspace')
            ->get();

        $selectableWorkspaces = $memberships
            ->map(fn (WorkspaceMembership $membership) => $membership->workspace)
            ->filter(fn (?Workspace $workspace) => $workspace instanceof Workspace && $this->isWorkspaceSelectable($workspace))
            ->values();

        if ($selectableWorkspaces->count() === 1) {
            /** @var Workspace $workspace */
            $workspace = $selectableWorkspaces->first();

            $session->put(self::SESSION_KEY, (int) $workspace->getKey());
            $user->forceFill(['last_workspace_id' => (int) $workspace->getKey()])->save();

            return $workspace;
        }

        return null;
    }

    public function isMember(User $user, Workspace $workspace): bool
    {
        return WorkspaceMembership::query()
            ->where('user_id', $user->getKey())
            ->where('workspace_id', $workspace->getKey())
            ->exists();
    }

    public function currentWorkspaceForMemberOrFail(User $user, ?Request $request = null): Workspace
    {
        $workspace = $this->currentWorkspace($request);

        if (! $workspace instanceof Workspace || ! $this->isMember($user, $workspace)) {
            throw new NotFoundHttpException;
        }

        return $workspace;
    }

    public function ensureTenantAccessibleInCurrentWorkspace(ManagedEnvironment $tenant, User $user, ?Request $request = null): ManagedEnvironment
    {
        $workspace = $this->currentWorkspaceForMemberOrFail($user, $request);

        if ((int) $tenant->workspace_id !== (int) $workspace->getKey() || $tenant->isRemovedFromWorkspace() || ! $user->canAccessTenant($tenant)) {
            throw new NotFoundHttpException;
        }

        return $tenant;
    }

    private function isWorkspaceSelectable(Workspace $workspace): bool
    {
        return $workspace->isSelectableAsContext();
    }

    private function userCanAccessTenant(ManagedEnvironment $tenant, ?Request $request = null): bool
    {
        $user = $request?->user();

        if (! $user instanceof User) {
            $user = auth()->user();
        }

        return $user instanceof User && $user->canAccessTenant($tenant);
    }
}
