<?php

declare(strict_types=1);

namespace Tests\Feature\Demo;

use App\Console\Commands\ResetDemoCompany;
use App\Models\Client;
use App\Models\Company;
use App\Models\DemoCredential;
use App\Models\Plan;
use App\Models\User;
use App\Support\Demo;
use Database\Seeders\DemoDataSeeder;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use PHPUnit\Framework\Attributes\Test;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * `credify:reset-demo` es un comando de BORRADO MASIVO: vacía y repuebla la
 * empresa demo compartida. La seguridad es lo único que importa aquí — en
 * particular, `test_reset_never_touches_a_real_company` es la prueba
 * estrella: si algún día el allowlist o los guards se rompen, esta prueba
 * debe ser la primera en fallar, ANTES de que el comando corra en producción
 * contra la base de datos compartida.
 */
class ResetDemoTest extends TestCase
{
    use DatabaseTransactions;

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

        foreach (['admin', 'supervisor', 'collector'] as $role) {
            Role::firstOrCreate(['name' => $role, 'guard_name' => 'web']);
        }

        Plan::where('slug', 'professional')->exists() || Plan::factory()->create([
            'slug' => 'professional',
            'is_active' => true,
            'max_clients' => 500,
        ]);

        // La BD compartida de dev/prod puede tener la demo YA poblada; la vaciamos
        // (dentro de la transacción del test → se revierte) para que los seeds
        // explícitos de estos tests no choquen con los `DEMO-*` persistidos.
        $demo = Company::where('is_demo', true)->first();
        if ($demo) {
            ResetDemoCompany::wipeTenantData($demo->id);
        }
    }

    #[Test]
    public function test_force_reset_wipes_demo_data_but_preserves_company_sub_users_and_reseeds(): void
    {
        // --no-seed: estos tests siembran explícitamente con $this->seed() más
        // abajo; sin la bandera, setup-demo ya sembraría y el segundo seed
        // chocaría con los `identification` únicos DEMO-*.
        $this->artisan('credify:setup-demo --no-seed')->assertSuccessful();

        $demo = Company::where('is_demo', true)->first();
        $this->assertNotNull($demo);

        $this->seed(DemoDataSeeder::class);

        $clientsBefore = Client::withoutGlobalScopes()->where('company_id', $demo->id)->count();
        $this->assertGreaterThan(0, $clientsBefore, 'la demo debe tener clientes antes del reset');

        $this->artisan('credify:reset-demo', ['--force' => true])->assertSuccessful();

        $demo = $demo->fresh();
        $this->assertNotNull($demo);
        $this->assertTrue((bool) $demo->is_demo);
        $this->assertNotNull($demo->currentSubscription);

        foreach (Demo::USERS as $email => $role) {
            $user = User::withoutGlobalScopes()->where('email', $email)->first();
            $this->assertNotNull($user, "el usuario demo {$email} debe seguir existiendo");
            $this->assertSame($demo->id, $user->company_id);
        }

        $clientsAfter = Client::withoutGlobalScopes()->where('company_id', $demo->id)->count();
        $this->assertGreaterThan(0, $clientsAfter, 'la demo debe quedar repoblada tras el reset');
    }

    #[Test]
    public function test_reset_never_touches_a_real_company(): void
    {
        // --no-seed: estos tests siembran explícitamente con $this->seed() más
        // abajo; sin la bandera, setup-demo ya sembraría y el segundo seed
        // chocaría con los `identification` únicos DEMO-*.
        $this->artisan('credify:setup-demo --no-seed')->assertSuccessful();

        $this->seed(DemoDataSeeder::class);

        $realCompany = Company::factory()->create(['is_demo' => false]);
        $realClient = Client::factory()->create(['company_id' => $realCompany->id]);

        $this->artisan('credify:reset-demo', ['--force' => true])->assertSuccessful();

        $this->assertDatabaseHas('companies', [
            'id' => $realCompany->id,
            'is_demo' => false,
        ]);

        $this->assertDatabaseHas('clients', [
            'id' => $realClient->id,
            'company_id' => $realCompany->id,
        ]);
    }

    #[Test]
    public function test_without_force_below_threshold_does_not_reset(): void
    {
        // --no-seed: estos tests siembran explícitamente con $this->seed() más
        // abajo; sin la bandera, setup-demo ya sembraría y el segundo seed
        // chocaría con los `identification` únicos DEMO-*.
        $this->artisan('credify:setup-demo --no-seed')->assertSuccessful();

        $demo = Company::where('is_demo', true)->first();
        $this->assertNotNull($demo);

        $this->seed(DemoDataSeeder::class);

        $clientsBefore = Client::withoutGlobalScopes()->where('company_id', $demo->id)->count();
        $this->assertGreaterThan(0, $clientsBefore);

        $this->artisan('credify:reset-demo', ['--threshold' => 90])->assertSuccessful();

        $clientsAfter = Client::withoutGlobalScopes()->where('company_id', $demo->id)->count();
        $this->assertSame($clientsBefore, $clientsAfter, 'sin --force y por debajo del umbral, no debe resetear');
    }

    #[Test]
    public function test_aborts_when_not_exactly_one_demo(): void
    {
        // La BD de dev compartida puede tener una empresa demo persistida; la
        // desactivamos dentro de la transacción del test (se revierte al final)
        // para ejercer el guard "exactamente 1" con 0 empresas demo.
        Company::where('is_demo', true)->update(['is_demo' => false]);
        $this->assertSame(0, Company::where('is_demo', true)->count());

        $this->artisan('credify:reset-demo', ['--force' => true])->assertFailed();
    }

    #[Test]
    public function force_reset_rotates_demo_passwords(): void
    {
        $this->artisan('credify:setup-demo --no-seed')->assertSuccessful();
        $before = DemoCredential::where('role', 'admin')->first()->password;

        $this->artisan('credify:reset-demo', ['--force' => true])->assertSuccessful();

        $after = DemoCredential::where('role', 'admin')->first()->password;
        $this->assertNotSame($before, $after);
    }
}
