<?php

/**
 * Session fixation prevention tests.
 *
 * Verifies that all authentication flows regenerate the session ID
 * after a successful login, preventing session fixation attacks.
 */

use App\Models\Role;
use App\Models\User;
use App\Services\SystemConfig\SystemConfigService;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\Session;
use Laravel\Socialite\Facades\Socialite;

beforeEach(function () {
    $ref = new ReflectionClass(SystemConfigService::class);
    $prop = $ref->getProperty('resolvedSettings');
    $prop->setAccessible(true);
    $prop->setValue(null, null);
    Cache::flush();
});

// ── Web login ─────────────────────────────────────────────────────────────────

test('web login regenerates the session id', function () {
    $user = User::factory()->create();
    $before = session()->getId();

    $this->post('/login', [
        'email'    => $user->email,
        'password' => 'password',
    ]);

    // After login the session must have a different ID
    expect(session()->getId())->not->toBe($before);
});

// ── 2FA verify ────────────────────────────────────────────────────────────────

test('2fa verify regenerates the session id', function () {
    $user = User::factory()->create();
    Session::put('auth.2fa_user_id', $user->id);
    Session::put('auth.2fa_code', '123456');
    Session::put('auth.2fa_expires_at', now()->addMinutes(10)->timestamp);

    $before = session()->getId();

    $this->post('/2fa', ['code' => '123456']);

    expect(session()->getId())->not->toBe($before);
});

// ── OAuth / Socialite callback ────────────────────────────────────────────────

test('oauth callback regenerates the session id after successful login', function () {
    Role::firstOrCreate(['name' => 'User', 'guard_name' => 'web']);
    app(SystemConfigService::class)->update(['feature_google_oauth' => true]);
    session(['social_auth_provider' => 'google']);

    $socialUser = new Laravel\Socialite\Two\User;
    $socialUser->id     = 'google-fixation-id';
    $socialUser->name   = 'Fixation User';
    $socialUser->email  = 'fixation@example.com';
    $socialUser->avatar = '';
    $socialUser->user   = [];

    Socialite::shouldReceive('driver->user')->andReturn($socialUser);

    $before = session()->getId();

    $this->get('/auth/callback')->assertRedirect('/dashboard');

    expect(session()->getId())->not->toBe($before);
});

// ── Password reset ────────────────────────────────────────────────────────────

test('password reset regenerates the session id', function () {
    Notification::fake();
    $user = User::factory()->create();

    // Request a reset token
    $this->post('/forgot-password', ['email' => $user->email]);
    $token = Password::broker()->createToken($user);

    $before = session()->getId();

    $this->post('/reset-password', [
        'token'                 => $token,
        'email'                 => $user->email,
        'password'              => 'NewSecurePass1!',
        'password_confirmation' => 'NewSecurePass1!',
    ])->assertRedirect(route('login'));

    expect(session()->getId())->not->toBe($before);
});

// ── Impersonation ─────────────────────────────────────────────────────────────

test('starting impersonation regenerates the session id', function () {
    $perm = \App\Models\Permission::firstOrCreate(['name' => 'impersonate users', 'guard_name' => 'web']);
    $admin = User::factory()->create(['is_active' => true]);
    $admin->givePermissionTo($perm);
    $target = User::factory()->create(['is_active' => true]);

    $before = session()->getId();

    $this->actingAs($admin)->post("/impersonate/{$target->id}");

    expect(session()->getId())->not->toBe($before);
});

test('stopping impersonation regenerates the session id', function () {
    $perm = \App\Models\Permission::firstOrCreate(['name' => 'impersonate users', 'guard_name' => 'web']);
    $admin = User::factory()->create(['is_active' => true]);
    $admin->givePermissionTo($perm);
    $target = User::factory()->create(['is_active' => true]);

    // Start impersonation first
    $this->actingAs($admin)->post("/impersonate/{$target->id}");

    $before = session()->getId();

    $this->post('/impersonate/stop');

    expect(session()->getId())->not->toBe($before);
});
