<?php

namespace App\Modules\Settings\Rbac\Services;

use App\Models\User;
use App\Modules\Admin\Tenancy\Models\Tenant;
use App\Modules\Navigation\Services\NavigationService;
use App\Modules\Settings\Rbac\DTOs\RoleWriteData;
use App\Modules\Settings\Rbac\Models\Role;
use App\Modules\Settings\Rbac\Repositories\PermissionRepository;
use App\Modules\Settings\Rbac\Repositories\RoleRepository;
use App\Modules\Settings\Rbac\Repositories\TenantUserRepository;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class TenantRoleManagementService
{
    private const OWNER_SLUG = 'owner';

    public function __construct(
        private readonly RoleRepository $roles,
        private readonly PermissionRepository $permissions,
        private readonly TenantUserRepository $users,
        private readonly RbacAuditService $audit,
    ) {}

    public function list(Tenant $tenant): Collection
    {
        return $this->roles->forTenant((int) $tenant->id);
    }

    public function paginateForIndex(Tenant $tenant, Request $request): LengthAwarePaginator
    {
        return $this->roles->paginateForIndex((int) $tenant->id, $request);
    }

    public function listAssignable(Tenant $tenant): Collection
    {
        return $this->roles->activeForTenant((int) $tenant->id);
    }

    public function find(Tenant $tenant, int $roleId): ?Role
    {
        return $this->roles->findForTenant((int) $tenant->id, $roleId);
    }

    public function create(Tenant $tenant, RoleWriteData $data): Role
    {
        $role = DB::transaction(function () use ($tenant, $data): Role {
            $role = $this->roles->create([
                'tenant_id' => $tenant->id,
                'name' => $data->name,
                'slug' => $this->uniqueSlug((int) $tenant->id, $data->name),
                'description' => $data->description,
                'is_active' => $data->isActive,
            ]);
            $this->permissions->attachToRole((int) $role->id, $data->permissionIds);

            return $this->roles->refresh($role);
        });
        $this->audit->role($role, 'created');

        return $role;
    }

    public function update(Tenant $tenant, Role $role, RoleWriteData $data): Role
    {
        $this->assertTenant($tenant, $role);
        $isOwner = $role->slug === self::OWNER_SLUG;
        $updated = DB::transaction(function () use ($role, $data, $isOwner): Role {
            $updated = $this->roles->update($role, [
                'name' => $data->name,
                'description' => $data->description,
                'is_active' => $isOwner ? true : $data->isActive,
            ]);
            $this->permissions->attachToRole((int) $role->id, $data->permissionIds);

            return $this->roles->refresh($updated);
        });
        $this->forgetTenantUsers((int) $tenant->id);
        $this->audit->role($updated, 'updated');

        return $updated;
    }

    public function delete(Tenant $tenant, Role $role): void
    {
        $this->assertTenant($tenant, $role);
        if ($role->slug === self::OWNER_SLUG) {
            throw ValidationException::withMessages([
                'role' => [__('rbac.validation.owner_role_protected')],
            ]);
        }
        if ($this->roles->hasAssignedUsers($role)) {
            throw ValidationException::withMessages([
                'role' => [__('rbac.validation.role_assigned')],
            ]);
        }
        $this->roles->delete($role);
        $this->forgetTenantUsers((int) $tenant->id);
        $this->audit->role($role, 'deleted');
    }

    private function uniqueSlug(int $tenantId, string $name): string
    {
        $base = Str::slug($name) ?: 'role';
        $slug = $base;
        $suffix = 2;
        while ($this->roles->slugExists($tenantId, $slug)) {
            $slug = $base.'-'.$suffix++;
        }

        return $slug;
    }

    private function assertTenant(Tenant $tenant, Role $role): void
    {
        abort_if((int) $role->tenant_id !== (int) $tenant->id, 404);
    }

    private function forgetTenantUsers(int $tenantId): void
    {
        $this->users->eachForTenant($tenantId, function (User $user): void {
            TenantPermissionEvaluator::forgetCacheForUser($user);
            NavigationService::forgetForUser($user);
        });
    }
}
