<?php

declare(strict_types=1);

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Socialite\Contracts\Factory as SocialiteFactory;
use Laravel\Socialite\Contracts\Provider;
use Laravel\Socialite\Two\User as OAuthUser;
use Maestro\Core\Database\Seeders\UserSeeder;
use Tests\Fixtures\AuthTestUser;
use Tests\TestCase;

class MicrosoftAuthTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        $this->app['config']->set('services.microsoft.client_id', 'test-client');
        $this->app['config']->set('services.microsoft.client_secret', 'test-secret');
        $this->app['config']->set('services.microsoft.redirect', 'http://localhost/auth/microsoft/callback');
        $this->app['config']->set('services.microsoft.tenant', 'common');
        $this->app['config']->set('services.microsoft.frontend_url', 'http://localhost');
    }

    public function test_microsoft_redirect_sends_user_to_authorization_endpoint(): void
    {
        $provider = \Mockery::mock(Provider::class);
        $provider->shouldReceive('scopes')->withAnyArgs()->once()->andReturnSelf();
        $provider->shouldReceive('redirect')->once()->andReturn(
            redirect()->away('https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=test')
        );

        $factory = \Mockery::mock(SocialiteFactory::class);
        $factory->shouldReceive('driver')->with('microsoft')->andReturn($provider);
        $this->app->instance(SocialiteFactory::class, $factory);

        $response = $this->get('/auth/microsoft/redirect');
        $response->assertRedirect();
        $location = $response->headers->get('Location');
        self::assertNotNull($location);
        self::assertStringContainsString('login.microsoftonline.com', $location);
    }

    public function test_microsoft_callback_logs_in_pre_provisioned_user(): void
    {
        $this->seed(UserSeeder::class);
        $user = AuthTestUser::query()->where('email', 'demo@example.com')->first();
        self::assertNotNull($user);
        $user->forceFill([
            'microsoft_user_id' => 'azure-oid-demo-1',
            'identity_source' => 'microsoft',
        ])->save();

        $oauthUser = new OAuthUser;
        $oauthUser->id = 'azure-oid-demo-1';
        $oauthUser->email = 'demo@example.com';
        $oauthUser->name = 'Demo Admin';
        $oauthUser->token = 'access-token';

        $provider = \Mockery::mock(Provider::class);
        $provider->shouldReceive('user')->once()->andReturn($oauthUser);

        $factory = \Mockery::mock(SocialiteFactory::class);
        $factory->shouldReceive('driver')->with('microsoft')->andReturn($provider);
        $this->app->instance(SocialiteFactory::class, $factory);

        $response = $this->get('/auth/microsoft/callback?code=fake&state=fake');
        $response->assertRedirect('http://localhost/');
        $this->assertAuthenticatedAs($user, 'web');
    }

    public function test_microsoft_callback_denies_unknown_oid(): void
    {
        $this->seed(UserSeeder::class);

        $oauthUser = new OAuthUser;
        $oauthUser->id = 'unknown-oid';
        $oauthUser->email = 'x@y.com';
        $oauthUser->name = 'X';
        $oauthUser->token = 't';

        $provider = \Mockery::mock(Provider::class);
        $provider->shouldReceive('user')->once()->andReturn($oauthUser);

        $factory = \Mockery::mock(SocialiteFactory::class);
        $factory->shouldReceive('driver')->with('microsoft')->andReturn($provider);
        $this->app->instance(SocialiteFactory::class, $factory);

        $response = $this->get('/auth/microsoft/callback?code=fake');
        $response->assertRedirect();
        $loc = $response->headers->get('Location');
        self::assertNotNull($loc);
        self::assertStringContainsString('/auth/access', $loc);
        self::assertStringContainsString('message=', $loc);
        $this->assertGuest('web');
    }

    public function test_microsoft_redirect_fails_when_not_configured(): void
    {
        $this->app['config']->set('services.microsoft.client_id', '');
        $response = $this->get('/auth/microsoft/redirect');
        $response->assertRedirect();
        $loc = $response->headers->get('Location');
        self::assertNotNull($loc);
        self::assertStringContainsString('/auth/login', $loc);
        self::assertStringContainsString('error=', $loc);
    }
}
