<?php

namespace App\Services\Auth;

use App\Models\ManagedEnvironment;
use App\Models\ManagedEnvironmentMembership;
use App\Models\User;
use App\Models\Workspace;
use App\Models\WorkspaceMembership;
use App\Services\Intune\AuditLogger;
use App\Support\Audit\AuditActionId;
use App\Support\Auth\Capabilities;
use DomainException;
use Illuminate\Support\Facades\DB;

class ManagedEnvironmentMembershipManager
{
    private const string SCOPE_PLACEHOLDER_ROLE = 'readonly';

    public function __construct(
        public AuditLogger $auditLogger,
        private readonly WorkspaceCapabilityResolver $workspaceCapabilityResolver,
        private readonly ManagedEnvironmentAccessScopeResolver $managedEnvironmentAccessScopeResolver,
    ) {}

    public function addMember(
        ManagedEnvironment $tenant,
        User $actor,
        User $member,
        string $role,
        string $source = 'manual',
        ?string $sourceRef = null,
    ): ManagedEnvironmentMembership {
        return $this->grantScope(
            tenant: $tenant,
            actor: $actor,
            member: $member,
            source: $source,
            sourceRef: $sourceRef,
        );
    }

    public function grantScope(
        ManagedEnvironment $tenant,
        User $actor,
        User $member,
        string $source = 'manual',
        ?string $sourceRef = null,
    ): ManagedEnvironmentMembership {
        $workspace = $this->workspaceForTenant($tenant);
        $memberWorkspaceRole = $this->memberWorkspaceRole($workspace, $member);
        $this->assertActorCanManageScope($actor, $workspace);

        $membership = DB::transaction(function () use ($tenant, $actor, $member, $memberWorkspaceRole, $source, $sourceRef): ManagedEnvironmentMembership {
            $existing = ManagedEnvironmentMembership::query()
                ->where('managed_environment_id', $tenant->getKey())
                ->where('user_id', $member->getKey())
                ->first();

            if ($existing) {
                $existing->forceFill([
                    'role' => self::SCOPE_PLACEHOLDER_ROLE,
                    'source' => $source,
                    'source_ref' => $sourceRef,
                    'created_by_user_id' => (int) $actor->getKey(),
                ])->save();

                return $existing->refresh();
            }

            $membership = ManagedEnvironmentMembership::query()->create([
                'managed_environment_id' => (int) $tenant->getKey(),
                'user_id' => (int) $member->getKey(),
                'role' => self::SCOPE_PLACEHOLDER_ROLE,
                'source' => $source,
                'source_ref' => $sourceRef,
                'created_by_user_id' => (int) $actor->getKey(),
            ]);

            $this->auditLogger->log(
                tenant: $tenant,
                action: AuditActionId::ManagedEnvironmentAccessScopeGrant->value,
                context: [
                    'metadata' => [
                        'member_user_id' => (int) $member->getKey(),
                        'workspace_role' => $memberWorkspaceRole,
                        'source' => $source,
                    ],
                ],
                actorId: (int) $actor->getKey(),
                actorEmail: $actor->email,
                actorName: $actor->name,
                status: 'success',
                resourceType: 'tenant',
                resourceId: (string) $tenant->getKey(),
            );

            return $membership;
        });

        $this->managedEnvironmentAccessScopeResolver->clearCache();

        return $membership;
    }

    public function changeRole(ManagedEnvironment $tenant, User $actor, ManagedEnvironmentMembership $membership, string $newRole): ManagedEnvironmentMembership
    {
        throw new DomainException('Managed-environment access scopes do not manage roles. Change the workspace role instead.');
    }

    public function removeMember(ManagedEnvironment $tenant, User $actor, ManagedEnvironmentMembership $membership): void
    {
        $workspace = $this->workspaceForTenant($tenant);
        $this->assertActorCanManageScope($actor, $workspace);

        DB::transaction(function () use ($tenant, $actor, $membership): void {
            $membership->refresh();

            if ($membership->managed_environment_id !== (int) $tenant->getKey()) {
                throw new DomainException('Membership belongs to a different tenant.');
            }

            $memberUserId = (int) $membership->user_id;

            $membership->delete();

            $this->auditLogger->log(
                tenant: $tenant,
                action: AuditActionId::ManagedEnvironmentAccessScopeRemove->value,
                context: [
                    'metadata' => [
                        'member_user_id' => $memberUserId,
                    ],
                ],
                actorId: (int) $actor->getKey(),
                actorEmail: $actor->email,
                actorName: $actor->name,
                status: 'success',
                resourceType: 'tenant',
                resourceId: (string) $tenant->getKey(),
            );
        });

        $this->managedEnvironmentAccessScopeResolver->clearCache();
    }

    public function bootstrapRecover(ManagedEnvironment $tenant, User $actor, User $member): ManagedEnvironmentMembership
    {
        $membership = $this->addMember(
            tenant: $tenant,
            actor: $actor,
            member: $member,
            role: 'owner',
            source: 'break_glass',
        );

        $this->auditLogger->log(
            tenant: $tenant,
            action: AuditActionId::TenantMembershipBootstrapRecover->value,
            context: [
                'metadata' => [
                    'member_user_id' => (int) $member->getKey(),
                ],
            ],
            actorId: (int) $actor->getKey(),
            actorEmail: $actor->email,
            actorName: $actor->name,
            status: 'success',
            resourceType: 'tenant',
            resourceId: (string) $tenant->getKey(),
        );

        return $membership;
    }

    private function workspaceForTenant(ManagedEnvironment $tenant): Workspace
    {
        $workspace = $tenant->workspace;

        if (! $workspace instanceof Workspace) {
            $workspace = Workspace::query()->whereKey((int) $tenant->workspace_id)->first();
        }

        if (! $workspace instanceof Workspace) {
            throw new DomainException('Managed environment does not belong to a workspace.');
        }

        return $workspace;
    }

    private function assertActorCanManageScope(User $actor, Workspace $workspace): void
    {
        if (! $this->workspaceCapabilityResolver->can($actor, $workspace, Capabilities::WORKSPACE_MEMBERSHIP_MANAGE)) {
            throw new DomainException('Forbidden.');
        }
    }

    private function memberWorkspaceRole(Workspace $workspace, User $member): string
    {
        $membership = WorkspaceMembership::query()
            ->where('workspace_id', (int) $workspace->getKey())
            ->where('user_id', (int) $member->getKey())
            ->first(['role']);

        if (! $membership instanceof WorkspaceMembership) {
            throw new DomainException('Environment access scope can only be granted to workspace members.');
        }

        return (string) $membership->role;
    }
}
