<?php

declare(strict_types=1);

use App\Listeners\RecordFailedLogin;
use App\Listeners\RecordLoginHistory;
use App\Models\LoginHistory;
use App\Models\User;
use App\Notifications\NewDeviceLoginNotification;
use Database\Seeders\RoleAndPermissionSeeder;
use Illuminate\Auth\Events\Failed;
use Illuminate\Auth\Events\Login;
use Illuminate\Support\Facades\Notification;

beforeEach(function (): void {
    $this->seed(RoleAndPermissionSeeder::class);
    $this->accountUrl = 'http://'.config('app.domains.account');
    $this->adminUrl = 'http://'.config('app.domains.admin');
});

// ---------------------------------------------------------------------------
// Device Hash
// ---------------------------------------------------------------------------
describe('Device Hash', function (): void {
    it('generates consistent device hash for same inputs', function (): void {
        $hash1 = LoginHistory::generateDeviceHash('Mozilla/5.0 Chrome', '192.168.1.100');
        $hash2 = LoginHistory::generateDeviceHash('Mozilla/5.0 Chrome', '192.168.1.100');

        expect($hash1)->toBe($hash2)
            ->and(strlen($hash1))->toBe(64);
    });

    it('groups similar IPs using first 3 octets', function (): void {
        $hash1 = LoginHistory::generateDeviceHash('Mozilla/5.0 Chrome', '192.168.1.100');
        $hash2 = LoginHistory::generateDeviceHash('Mozilla/5.0 Chrome', '192.168.1.200');

        expect($hash1)->toBe($hash2);
    });

    it('produces different hash for different user agents', function (): void {
        $hash1 = LoginHistory::generateDeviceHash('Mozilla/5.0 Chrome', '192.168.1.100');
        $hash2 = LoginHistory::generateDeviceHash('Mozilla/5.0 Firefox', '192.168.1.100');

        expect($hash1)->not->toBe($hash2);
    });

    it('handles IPv6 addresses gracefully', function (): void {
        $hash = LoginHistory::generateDeviceHash('Mozilla/5.0', '2001:0db8:85a3:0000:0000:8a2e:0370:7334');

        expect($hash)->toBeString()
            ->and(strlen($hash))->toBe(64);
    });
});

// ---------------------------------------------------------------------------
// Login Recording
// ---------------------------------------------------------------------------
describe('Login Recording', function (): void {
    it('creates a history record on successful login', function (): void {
        Notification::fake();
        $user = User::factory()->customer()->create();

        $event = new Login('web', $user, false);
        $listener = app(RecordLoginHistory::class);
        $listener->handle($event);

        expect(LoginHistory::query()->count())->toBe(1);

        $record = LoginHistory::query()->first();
        expect($record->user_id)->toBe($user->id)
            ->and($record->success)->toBeTrue()
            ->and($record->device_hash)->not->toBeNull();
    });

    it('creates a record with success=false on failed login', function (): void {
        $user = User::factory()->customer()->create();

        $event = new Failed('web', $user, ['email' => $user->email, 'password' => 'wrong']);
        $listener = app(RecordFailedLogin::class);
        $listener->handle($event);

        expect(LoginHistory::query()->count())->toBe(1);

        $record = LoginHistory::query()->first();
        expect($record->user_id)->toBe($user->id)
            ->and($record->success)->toBeFalse();
    });

    it('skips failed login recording when user not found', function (): void {
        $event = new Failed('web', null, ['email' => 'nonexistent@example.com', 'password' => 'wrong']);
        $listener = app(RecordFailedLogin::class);
        $listener->handle($event);

        expect(LoginHistory::query()->count())->toBe(0);
    });
});

// ---------------------------------------------------------------------------
// New Device Detection
// ---------------------------------------------------------------------------
describe('New Device Detection', function (): void {
    it('does not flag first-ever login as new device', function (): void {
        Notification::fake();
        $user = User::factory()->customer()->create();

        $event = new Login('web', $user, false);
        $listener = app(RecordLoginHistory::class);
        $listener->handle($event);

        $record = LoginHistory::query()->first();
        expect($record->is_new_device)->toBeFalse();
    });

    it('flags login from different device as new', function (): void {
        Notification::fake();
        $user = User::factory()->customer()->create();

        // Create initial login history
        LoginHistory::factory()->create([
            'user_id' => $user->id,
            'device_hash' => LoginHistory::generateDeviceHash('OldBrowser/1.0', '10.0.0.1'),
        ]);

        // New login event with different user agent (from request)
        $event = new Login('web', $user, false);
        $listener = app(RecordLoginHistory::class);
        $listener->handle($event);

        $newRecord = LoginHistory::query()
            ->where('user_id', $user->id)
            ->latest('id')
            ->first();

        expect($newRecord->is_new_device)->toBeTrue();
    });

    it('does not flag same device as new', function (): void {
        Notification::fake();
        $user = User::factory()->customer()->create();

        // First login
        $event = new Login('web', $user, false);
        $listener = app(RecordLoginHistory::class);
        $listener->handle($event);

        // Second login from same device (same request context)
        $listener->handle($event);

        $records = LoginHistory::query()->where('user_id', $user->id)->get();
        expect($records)->toHaveCount(2);
        expect($records[0]->is_new_device)->toBeFalse();
        expect($records[1]->is_new_device)->toBeFalse();
    });
});

// ---------------------------------------------------------------------------
// Notifications
// ---------------------------------------------------------------------------
describe('New Device Notification', function (): void {
    it('sends notification when new device detected with previous logins', function (): void {
        Notification::fake();
        $user = User::factory()->customer()->create();

        // Create a prior login with a different device hash
        LoginHistory::factory()->create([
            'user_id' => $user->id,
            'device_hash' => LoginHistory::generateDeviceHash('OldBrowser/1.0', '10.0.0.1'),
        ]);

        $event = new Login('web', $user, false);
        $listener = app(RecordLoginHistory::class);
        $listener->handle($event);

        Notification::assertSentTo($user, NewDeviceLoginNotification::class);
    });

    it('does not send notification on first-ever login', function (): void {
        Notification::fake();
        $user = User::factory()->customer()->create();

        $event = new Login('web', $user, false);
        $listener = app(RecordLoginHistory::class);
        $listener->handle($event);

        Notification::assertNotSentTo($user, NewDeviceLoginNotification::class);
    });
});

// ---------------------------------------------------------------------------
// Customer Pages
// ---------------------------------------------------------------------------
describe('Customer Login History Page', function (): void {
    it('allows customer to view their login history page', function (): void {
        $customer = User::factory()->customer()->create();

        LoginHistory::factory()->count(3)->create(['user_id' => $customer->id]);

        $this->actingAs($customer)
            ->get($this->accountUrl.'/login-history')
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('Profile/LoginHistory')
                ->has('loginHistories.data', 3)
            );
    });

    it('only shows the current users login history', function (): void {
        $customer = User::factory()->customer()->create();
        $other = User::factory()->customer()->create();

        LoginHistory::factory()->count(2)->create(['user_id' => $customer->id]);
        LoginHistory::factory()->count(5)->create(['user_id' => $other->id]);

        $this->actingAs($customer)
            ->get($this->accountUrl.'/login-history')
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->has('loginHistories.data', 2)
            );
    });

    it('requires authentication to view login history', function (): void {
        $this->get($this->accountUrl.'/login-history')
            ->assertRedirect();
    });

    it('paginates login history entries', function (): void {
        $customer = User::factory()->customer()->create();

        LoginHistory::factory()->count(20)->create(['user_id' => $customer->id]);

        $this->actingAs($customer)
            ->get($this->accountUrl.'/login-history')
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->has('loginHistories.data', 15)
                ->where('loginHistories.total', 20)
                ->where('loginHistories.last_page', 2)
            );
    });
});

// ---------------------------------------------------------------------------
// Profile Security Tab
// ---------------------------------------------------------------------------
describe('Profile Security Tab', function (): void {
    it('includes login histories in profile page props', function (): void {
        $customer = User::factory()->customer()->create();

        LoginHistory::factory()->count(5)->create(['user_id' => $customer->id]);

        $this->actingAs($customer)
            ->get($this->accountUrl.'/profile')
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->has('loginHistories', 5)
            );
    });
});

// ---------------------------------------------------------------------------
// Admin Customer Show
// ---------------------------------------------------------------------------
describe('Admin Customer Show', function (): void {
    it('includes login histories on admin customer show page', function (): void {
        $admin = User::factory()->admin()->create();
        $customer = User::factory()->customer()->create();

        LoginHistory::factory()->count(10)->create(['user_id' => $customer->id]);

        $this->actingAs($admin)
            ->get($this->adminUrl.'/customers/'.$customer->id)
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('Admin/Customers/Show')
                ->has('loginHistories', 10)
            );
    });
});

// ---------------------------------------------------------------------------
// Scopes
// ---------------------------------------------------------------------------
describe('Scopes', function (): void {
    it('filters successful logins', function (): void {
        $user = User::factory()->customer()->create();

        LoginHistory::factory()->count(3)->create(['user_id' => $user->id, 'success' => true]);
        LoginHistory::factory()->count(2)->failed()->create(['user_id' => $user->id]);

        expect(LoginHistory::query()->successful()->count())->toBe(3);
    });

    it('filters failed logins', function (): void {
        $user = User::factory()->customer()->create();

        LoginHistory::factory()->count(3)->create(['user_id' => $user->id, 'success' => true]);
        LoginHistory::factory()->count(2)->failed()->create(['user_id' => $user->id]);

        expect(LoginHistory::query()->failed()->count())->toBe(2);
    });

    it('filters by user id', function (): void {
        $user1 = User::factory()->customer()->create();
        $user2 = User::factory()->customer()->create();

        LoginHistory::factory()->count(3)->create(['user_id' => $user1->id]);
        LoginHistory::factory()->count(5)->create(['user_id' => $user2->id]);

        expect(LoginHistory::query()->forUser($user1->id)->count())->toBe(3);
    });
});
