<?php

declare(strict_types=1);

namespace Tests\Unit;

use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use PHPUnit\Framework\Attributes\Test;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Verifica que el sistema de roles usa exclusivamente Spatie Permission
 * (model_has_roles) como fuente de verdad — sin columna `role` en users.
 *
 * Cubre tres invariantes:
 * (a) Un User recién creado via factory no tiene roles Spatie asignados.
 * (b) assignRole() persiste el rol y hasRole() lo detecta correctamente.
 * (c) Pasar 'role' en mass assignment no rompe silenciosamente: el campo
 *     es ignorado por no estar en $fillable, y el usuario queda sin rol.
 */
class UserRoleTest extends TestCase
{
    use DatabaseTransactions;

    protected function setUp(): void
    {
        parent::setUp();

        // Garantizar que los roles Spatie existen antes de cada test.
        // InitialSetupSeeder los crea en producción; aquí los creamos inline
        // para que los tests de Unit no dependan de que el seeder haya corrido.
        foreach (['collector', 'supervisor', 'admin', 'super_admin'] as $role) {
            Role::firstOrCreate(['name' => $role, 'guard_name' => 'web']);
        }
    }

    #[Test]
    public function factory_user_has_no_spatie_roles_by_default(): void
    {
        $user = User::factory()->create();

        $this->assertEmpty(
            $user->getRoleNames(),
            'Un User creado via factory no debe tener roles Spatie hasta que se llame assignRole().'
        );
    }

    #[Test]
    public function assign_role_is_detected_by_has_role(): void
    {
        $user = User::factory()->create();

        $this->assertFalse($user->hasRole('collector'));

        $user->assignRole('collector');

        // Recargar para descartar cache en memoria del trait HasRoles.
        $user->refresh();

        $this->assertTrue(
            $user->hasRole('collector'),
            'hasRole() debe retornar true tras assignRole().'
        );
        $this->assertTrue(
            $user->getRoleNames()->contains('collector'),
            'getRoleNames() debe incluir el rol asignado.'
        );
    }

    #[Test]
    public function role_is_not_fillable_and_fill_discards_it_silently(): void
    {
        // Nota: Factory::create() usa Model::unguarded() internamente, lo que
        // desactiva $fillable temporalmente. Por eso el guard se prueba con fill()
        // o new User([...]) directamente — no con el factory.

        // (i) 'role' no está declarado en $fillable.
        $this->assertNotContains(
            'role',
            (new User)->getFillable(),
            "'role' no debe estar en \$fillable: la columna no existe en la tabla users."
        );

        // (ii) fill() descarta silenciosamente el campo sin lanzar excepción.
        $user = new User;
        $user->fill(['role' => 'collector', 'name' => 'Test', 'email' => 'test@example.com']);

        $this->assertArrayNotHasKey(
            'role',
            $user->getAttributes(),
            "fill() no debe colocar 'role' en los atributos del modelo."
        );

        // (iii) El campo ignorado no asigna ningún rol Spatie.
        $this->assertEmpty(
            $user->getRoleNames(),
            "fill() con 'role' no debe producir efectos secundarios en Spatie."
        );
    }
}
