<?php

namespace Tests\Feature\Middleware;

use App\Master\Models\ApiToken;
use App\Services\ApiUsageTracker;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Http\Request;
use App\Http\Middleware\ApiAuthenticatedRequest;
use Tests\DatabaseTestCase;

class ApiAuthenticatedRequestTest extends DatabaseTestCase
{
    #[\Override]
    protected function setUp(): void
    {
        parent::setUp();
        $this->middleware = new ApiAuthenticatedRequest();
    }

    public function testDeniesAccessIfNoAuthorizationHeader(): void
    {
        $request = Request::create('/dummy', 'GET');
        $response = $this->getResponse($request);

        $this->assertEquals(401, $response->getStatusCode());
        $this->assertStringContainsString('Access Denied', $response->getContent());
    }

    public function testDeniesAccessWithInvalidToken(): void
    {
        $request = Request::create('/dummy', 'GET');
        $request->headers->set('Authorization', 'Bearer invalidtoken');
        $response = $this->getResponse($request);

        $this->assertEquals(401, $response->getStatusCode());
        $this->assertStringContainsString('Access Denied', $response->getContent());
    }

    public function testDeniesAccessWithExpiredToken(): void
    {
        tenant()->saveQuietly();
        $apiToken = ApiToken::factory()->expired()->create();
        $apiToken->apiUser->tenants()->attach(tenant('id'));

        $request = Request::create('/dummy', 'GET');
        $request->headers->set('Authorization', 'Bearer ' . $apiToken->token);

        $response = $this->getResponse($request);

        $this->assertEquals(401, $response->getStatusCode());
        $this->assertStringContainsString('Access Denied', $response->getContent());
    }

    public function testDeniesAccessIfTokenHasNoTenantAccess(): void
    {
        tenant()->saveQuietly();
        $apiToken = ApiToken::factory()->create();
        $apiToken->apiUser->tenants()->attach('some-other-tenant');

        $request = Request::create('/dummy', 'GET');
        $request->headers->set('Authorization', 'Bearer ' . $apiToken->token);

        $response = $this->getResponse($request);

        $this->assertEquals(401, $response->getStatusCode());
    }

    public function testAllowsAccessWithValidToken(): void
    {
        tenant()->saveQuietly();
        $apiToken = ApiToken::factory()->create();
        $apiToken->apiUser->tenants()->attach(tenant('id'));

        $request = Request::create('/dummy', 'GET');
        $request->headers->set('Authorization', 'Bearer ' . $apiToken->token);
        $response = $this->getResponse($request);

        $this->assertEquals(200, $response->getStatusCode());
    }

    public function testRecordsApiUsageForAuthenticatedRequest(): void
    {
        tenant()->saveQuietly();
        $apiToken = ApiToken::factory()->create();
        $apiToken->apiUser->tenants()->attach(tenant('id'));

        $tracker = \Mockery::mock(ApiUsageTracker::class);
        $tracker->shouldReceive('record')->once();
        $this->app->instance(ApiUsageTracker::class, $tracker);

        $request = Request::create('/dummy', 'POST', ['foo' => 'bar']);
        $request->headers->set('Authorization', 'Bearer ' . $apiToken->token);

        (new ApiAuthenticatedRequest())->handle($request, fn () => response('ok'));
    }

    public function testAllowsExportMapAccessWithQueryParameterKey(): void
    {
        tenant()->saveQuietly();
        $apiToken = ApiToken::factory()->create();
        $apiToken->apiUser->tenants()->attach(tenant('id'));

        $request = Request::create('/dummy?key=' . $apiToken->token, 'GET');
        $response = $this->getResponse($request, 'export-map');

        $this->assertEquals(200, $response->getStatusCode());
    }

    public function testAllowsExportMapAccessWithBearerToken(): void
    {
        tenant()->saveQuietly();
        $apiToken = ApiToken::factory()->create();
        $apiToken->apiUser->tenants()->attach(tenant('id'));

        $request = Request::create('/dummy', 'GET');
        $request->headers->set('Authorization', 'Bearer ' . $apiToken->token);
        $response = $this->getResponse($request, 'export-map');

        $this->assertEquals(200, $response->getStatusCode());
    }

    public function testDeniesExportMapAccessWithInvalidKey(): void
    {
        $request = Request::create('/dummy?key=invalidtoken', 'GET');
        $response = $this->getResponse($request, 'export-map');

        $this->assertEquals(401, $response->getStatusCode());
    }

    public function testCachesSuccessfulTokenLookupToAvoidRepeatQueries(): void
    {
        tenant()->saveQuietly();
        $apiToken = ApiToken::factory()->create();
        $apiToken->apiUser->tenants()->attach(tenant('id'));

        $makeRequest = function () use ($apiToken) {
            $request = Request::create('/dummy', 'GET');
            $request->headers->set('Authorization', 'Bearer ' . $apiToken->token);
            return $this->getResponse($request);
        };

        // First call performs the lookup; subsequent calls within the TTL are served
        // from cache and run no further token queries.
        $this->assertEquals(200, $makeRequest()->getStatusCode());

        \DB::enableQueryLog();
        $this->assertEquals(200, $makeRequest()->getStatusCode());
        $queries = collect(\DB::getQueryLog())
            ->filter(fn ($q) => str_contains($q['query'], 'api_tokens') || str_contains($q['query'], 'api_user'));
        \DB::disableQueryLog();

        $this->assertCount(0, $queries, 'Cached token check should issue no token/tenant queries');
    }

    private function getResponse($request, ?string $legacyScenario = null): Response
    {
        return $this->middleware->handle($request, fn () => response('ok'), $legacyScenario);
    }
}
