<?php

namespace Tests\Tenant\Controllers;

use App\Facades\Settings;
use App\Models\ExternalApiToken;
use App\Modules\VehiclesInVideo\Services\TokenService;
use App\Modules\VehiclesInVideo\Services\VehiclesInVideoApiService;
use Database\Seeders\Tenant\VehiclesInVideoSettingSeeder;
use Mockery\MockInterface;
use Tests\TenantTestCase;
use Tests\UserForTenant;

class VehiclesInVideoControllerTest extends TenantTestCase
{
    use UserForTenant;

    protected function setUp(): void
    {
        parent::setUp();
        $this->seed(VehiclesInVideoSettingSeeder::class);
        Settings::update('vehicles-in-video-enabled', true);
        Settings::update('vehicles-in-video-api-key', 'test-api-key');
        Settings::update('vehicles-in-video-client-id', 'test-client');
        $this->getUser();
    }

    public function testTokenReturnsNullWhenNoTokenExists(): void
    {
        $response = $this->getJson(route('tenant.vehicles-in-video.token', tenant('id')));

        $response->assertStatus(200);
        $response->assertJson(['token' => null]);
    }

    public function testTokenReturnsConnectedShapeWhenTokenExists(): void
    {
        $token = ExternalApiToken::create([
            'provider' => TokenService::PROVIDER,
            'grant_url' => 'https://example.com/grant',
            'auth_token' => 'auth-abc',
            'refresh_token' => 'refresh-xyz',
            'auth_expires_at' => now()->addHour(),
            'refresh_expires_at' => now()->addDays(180),
        ]);

        $response = $this->getJson(route('tenant.vehicles-in-video.token', tenant('id')));

        $response->assertStatus(200);
        $response->assertJsonPath('token.id', $token->id);
        $response->assertJsonPath('token.connected', true);
        $response->assertJsonPath('token.grant_url', 'https://example.com/grant');
        $response->assertJsonStructure(['token' => ['refresh_expires_at']]);
    }

    public function testTokenReturnsDisconnectedShapeWhenOnlyGrantUrlPresent(): void
    {
        ExternalApiToken::create([
            'provider' => TokenService::PROVIDER,
            'grant_url' => 'https://example.com/grant',
        ]);

        $response = $this->getJson(route('tenant.vehicles-in-video.token', tenant('id')));

        $response->assertStatus(200);
        $response->assertJsonPath('token.connected', false);
        $response->assertJsonPath('token.grant_url', 'https://example.com/grant');
    }

    public function testTokenIgnoresOtherProviderTokens(): void
    {
        ExternalApiToken::create([
            'provider' => 'some-other-provider',
            'grant_url' => 'https://other.example.com/grant',
            'auth_token' => 'other-auth',
            'refresh_token' => 'other-refresh',
        ]);

        $response = $this->getJson(route('tenant.vehicles-in-video.token', tenant('id')));

        $response->assertStatus(200);
        $response->assertJson(['token' => null]);
    }

    public function testRequestGrantUrlStoresUrlForProvider(): void
    {
        ExternalApiToken::create([
            'provider' => TokenService::PROVIDER,
            'grant_url' => 'old',
        ]);
        ExternalApiToken::create([
            'provider' => TokenService::PROVIDER,
            'grant_url' => 'older',
        ]);
        $otherProvider = ExternalApiToken::create([
            'provider' => 'some-other-provider',
            'grant_url' => 'keep-me',
        ]);

        $this->mockApi(function (MockInterface $mock) {
            $mock->shouldReceive('requestGrantUrl')
                ->once()
                ->andReturn(['token_grant_url' => 'https://example.com/new-grant']);
        });

        $response = $this->postJson(route('tenant.vehicles-in-video.token.request', tenant('id')));

        $response->assertStatus(200);
        $response->assertJsonPath('success', true);
        $response->assertJsonPath('grant_url', 'https://example.com/new-grant');

        $this->assertEquals(
            1,
            ExternalApiToken::where('provider', TokenService::PROVIDER)->count()
        );
        $this->assertEquals(
            'https://example.com/new-grant',
            ExternalApiToken::where('provider', TokenService::PROVIDER)->latest()->first()->grant_url
        );
        $this->assertNotNull($otherProvider->fresh());
    }

    public function testRequestGrantUrlReturnsErrorFromApi(): void
    {
        $this->mockApi(function (MockInterface $mock) {
            $mock->shouldReceive('requestGrantUrl')
                ->once()
                ->andReturn(['error' => ['message' => 'API rejected the request']]);
        });

        $response = $this->postJson(route('tenant.vehicles-in-video.token.request', tenant('id')));

        $response->assertStatus(422);
        $response->assertJsonPath('success', false);
        $response->assertJsonPath('message', 'API rejected the request');
        $this->assertEquals(0, ExternalApiToken::count());
    }

    public function testRequestGrantUrlReturnsErrorWhenApiResponseMissingGrantUrl(): void
    {
        $this->mockApi(function (MockInterface $mock) {
            $mock->shouldReceive('requestGrantUrl')
                ->once()
                ->andReturn(['status_code' => 200]);
        });

        $response = $this->postJson(route('tenant.vehicles-in-video.token.request', tenant('id')));

        $response->assertStatus(422);
        $response->assertJsonPath('message', 'No grant URL received from API.');
        $this->assertEquals(0, ExternalApiToken::count());
    }

    public function testRefreshTokenSuccessfullyUpdatesStoredToken(): void
    {
        $token = ExternalApiToken::create([
            'provider' => TokenService::PROVIDER,
            'auth_token' => 'old-auth',
            'refresh_token' => 'old-refresh',
            'auth_expires_at' => now()->subHour(),
            'refresh_expires_at' => now()->addDays(180),
        ]);

        $this->mockApi(function (MockInterface $mock) {
            $mock->shouldReceive('refreshToken')
                ->once()
                ->with('old-refresh')
                ->andReturn([
                    'auth_token' => 'new-auth',
                    'refresh_token' => 'new-refresh',
                    'auth_token_expiry_secs' => 3600,
                    'refresh_token_expiry_secs' => 86400,
                ]);
        });

        $response = $this->postJson(route('tenant.vehicles-in-video.token.refresh', tenant('id')));

        $response->assertStatus(200);
        $response->assertJsonPath('success', true);

        $token->refresh();
        $this->assertEquals('new-auth', $token->auth_token);
        $this->assertEquals('new-refresh', $token->refresh_token);
        $this->assertTrue($token->auth_expires_at->isFuture());
    }

    public function testRefreshTokenWhenNoTokenExists(): void
    {
        $this->mockApi(function (MockInterface $mock) {
            $mock->shouldNotReceive('refreshToken');
        });

        $response = $this->postJson(route('tenant.vehicles-in-video.token.refresh', tenant('id')));

        $response->assertStatus(422);
        $response->assertJsonPath('message', 'No connected token found to refresh.');
    }

    public function testRefreshTokenWhenApiReturnsError(): void
    {
        ExternalApiToken::create([
            'provider' => TokenService::PROVIDER,
            'auth_token' => 'old-auth',
            'refresh_token' => 'old-refresh',
        ]);

        $this->mockApi(function (MockInterface $mock) {
            $mock->shouldReceive('refreshToken')
                ->andReturn(['error' => ['message' => 'Refresh denied']]);
        });

        $response = $this->postJson(route('tenant.vehicles-in-video.token.refresh', tenant('id')));

        $response->assertStatus(422);
        $response->assertJsonPath('message', 'Refresh denied');
    }

    public function testRefreshTokenWhenApiResponseIncomplete(): void
    {
        ExternalApiToken::create([
            'provider' => TokenService::PROVIDER,
            'auth_token' => 'old-auth',
            'refresh_token' => 'old-refresh',
        ]);

        $this->mockApi(function (MockInterface $mock) {
            $mock->shouldReceive('refreshToken')
                ->andReturn(['auth_token' => 'new-auth']);
        });

        $response = $this->postJson(route('tenant.vehicles-in-video.token.refresh', tenant('id')));

        $response->assertStatus(422);
        $response->assertJsonPath('message', 'Incomplete response from API.');
    }

    public function testDeleteTokensRemovesProviderTokensAndReportsCount(): void
    {
        ExternalApiToken::create(['provider' => TokenService::PROVIDER, 'grant_url' => 'a']);
        ExternalApiToken::create(['provider' => TokenService::PROVIDER, 'grant_url' => 'b']);
        $otherProvider = ExternalApiToken::create([
            'provider' => 'some-other-provider',
            'grant_url' => 'keep-me',
        ]);

        $response = $this->deleteJson(route('tenant.vehicles-in-video.token.delete', tenant('id')));

        $response->assertStatus(200);
        $response->assertJsonPath('success', true);
        $response->assertJsonPath('message', 'All tokens deleted (2 removed).');
        $this->assertEquals(
            0,
            ExternalApiToken::where('provider', TokenService::PROVIDER)->count()
        );
        $this->assertNotNull($otherProvider->fresh());
    }

    public function testDeleteTokensWhenNoneExist(): void
    {
        $response = $this->deleteJson(route('tenant.vehicles-in-video.token.delete', tenant('id')));

        $response->assertStatus(422);
        $response->assertJsonPath('message', 'No tokens to delete.');
    }

    private function mockApi(\Closure $expectations): void
    {
        $this->mock(VehiclesInVideoApiService::class, $expectations);
    }
}
