<?php

app_require('traits/economic_endpoint_t.php');
app_require('modules/economic/economic_m.php');

use traits\economic_endpoint_t;

if (!class_exists('EconomicEndpointGrantFallbackProbe')) {
    class EconomicEndpointGrantFallbackProbe
    {
        use economic_endpoint_t {
            resolve_agreement_grant_token as public resolveAgreementGrantToken;
            resolve_app_secret_token as public resolveAppSecretToken;
            assert_successful_response as public assertSuccessfulResponse;
        }
    }
}

if (!class_exists('EconomicMLegacyGrantFallbackProbe')) {
    class EconomicMLegacyGrantFallbackProbe extends economic_m
    {
        public function __construct()
        {
            // No-op: tests inject private token fields directly.
        }

        public function configureTokens(string $appSecretToken, string $primaryGrant, string $secondaryGrant = ''): void
        {
            $this->setPrivateTokenField('app_token', $appSecretToken);
            $this->setPrivateTokenField('appAccessGrant', $primaryGrant);
            $this->setPrivateTokenField('appAccessGrant2', $secondaryGrant);
        }

        public function resolveAgreementGrantToken(bool $authToken2): string
        {
            return $this->resolve_agreement_grant_token($authToken2);
        }

        public function resolveAppSecretToken(): string
        {
            return $this->resolve_app_secret_token();
        }

        public function assertSuccessfulResponse(int $httpStatusCode, string|false $response): string
        {
            return $this->assert_successful_response($httpStatusCode, $response);
        }

        private function setPrivateTokenField(string $fieldName, string $value): void
        {
            $reflection = new ReflectionProperty(economic_m::class, $fieldName);
            $reflection->setValue($this, $value);
        }
    }
}

beforeEach(function (): void {
    global $ECONOMIC_API;

    $ECONOMIC_API = [
        'app_secret_token' => 'test-secret',
        'app_access_grant' => 'primary-grant',
        'app_access_grant2' => 'secondary-grant',
    ];
});

it('uses grant #2 in economic_endpoint_t when explicitly requested and configured', function (): void {
    $probe = new EconomicEndpointGrantFallbackProbe();

    expect($probe->resolveAgreementGrantToken(true))->toBe('secondary-grant');
    expect($probe->resolveAgreementGrantToken(false))->toBe('primary-grant');
});

it('falls back to grant #1 in economic_endpoint_t when grant #2 is missing', function (): void {
    global $ECONOMIC_API;
    $ECONOMIC_API['app_access_grant2'] = '';

    $probe = new EconomicEndpointGrantFallbackProbe();

    expect($probe->resolveAgreementGrantToken(true))->toBe('primary-grant');
    expect($probe->resolveAgreementGrantToken(false))->toBe('primary-grant');
});

it('throws for missing required primary grant in economic_endpoint_t', function (): void {
    global $ECONOMIC_API;
    $ECONOMIC_API['app_access_grant'] = '';

    $probe = new EconomicEndpointGrantFallbackProbe();

    expect(fn() => $probe->resolveAgreementGrantToken(false))
        ->toThrow(RuntimeException::class, 'Missing e-conomic agreement grant token');
});

it('throws for missing app secret in economic_endpoint_t', function (): void {
    global $ECONOMIC_API;
    $ECONOMIC_API['app_secret_token'] = '';

    $probe = new EconomicEndpointGrantFallbackProbe();

    expect(fn() => $probe->resolveAppSecretToken())
        ->toThrow(RuntimeException::class, 'Missing e-conomic app secret token');
});

it('uses grant #2 in legacy economic_m when available', function (): void {
    $probe = new EconomicMLegacyGrantFallbackProbe();
    $probe->configureTokens('test-secret', 'primary-grant', 'secondary-grant');

    expect($probe->resolveAgreementGrantToken(true))->toBe('secondary-grant');
    expect($probe->resolveAgreementGrantToken(false))->toBe('primary-grant');
});

it('falls back to grant #1 in legacy economic_m when grant #2 is missing', function (): void {
    $probe = new EconomicMLegacyGrantFallbackProbe();
    $probe->configureTokens('test-secret', 'primary-grant', '');

    expect($probe->resolveAgreementGrantToken(true))->toBe('primary-grant');
});

it('throws for missing required primary grant in legacy economic_m', function (): void {
    $probe = new EconomicMLegacyGrantFallbackProbe();
    $probe->configureTokens('test-secret', '', 'secondary-grant');

    expect(fn() => $probe->resolveAgreementGrantToken(false))
        ->toThrow(RuntimeException::class, 'Missing e-conomic agreement grant token');
});

it('throws for missing app secret in legacy economic_m', function (): void {
    $probe = new EconomicMLegacyGrantFallbackProbe();
    $probe->configureTokens('', 'primary-grant', 'secondary-grant');

    expect(fn() => $probe->resolveAppSecretToken())
        ->toThrow(RuntimeException::class, 'Missing e-conomic app secret token');
});

it('throws deterministic upstream exceptions for non-2xx endpoint trait responses', function (): void {
    $probe = new EconomicEndpointGrantFallbackProbe();
    $errorPayload = json_encode([
        'message' => 'Could not parse query string filter.',
        'errors' => ['Filtering is not allowed on property \'invalidField\'.'],
        'logId' => 'abc123',
        'httpStatusCode' => 400,
    ]);

    expect(fn() => $probe->assertSuccessfulResponse(400, $errorPayload))
        ->toThrow(RuntimeException::class, 'Could not parse query string filter.');

    expect(fn() => $probe->assertSuccessfulResponse(502, 'Gateway timeout'))
        ->toThrow(RuntimeException::class, 'HTTP 502');
});

it('throws deterministic upstream exceptions for non-2xx legacy economic_m responses', function (): void {
    $probe = new EconomicMLegacyGrantFallbackProbe();
    $probe->configureTokens('test-secret', 'primary-grant', 'secondary-grant');
    $errorPayload = json_encode([
        'message' => 'Could not parse query string filter.',
        'errors' => ['Filtering is not allowed on property \'invalidField\'.'],
        'logId' => 'abc123',
        'httpStatusCode' => 400,
    ]);

    expect(fn() => $probe->assertSuccessfulResponse(400, $errorPayload))
        ->toThrow(RuntimeException::class, 'Could not parse query string filter.');

    expect(fn() => $probe->assertSuccessfulResponse(500, '<html>server error</html>'))
        ->toThrow(RuntimeException::class, 'HTTP 500');
});

it('returns raw payload unchanged on successful HTTP statuses', function (): void {
    $endpointProbe = new EconomicEndpointGrantFallbackProbe();
    $legacyProbe = new EconomicMLegacyGrantFallbackProbe();
    $legacyProbe->configureTokens('test-secret', 'primary-grant', 'secondary-grant');

    $payload = '{"collection":[{"customerNumber":1000}],"pagination":{"results":1}}';

    expect($endpointProbe->assertSuccessfulResponse(200, $payload))->toBe($payload);
    expect($legacyProbe->assertSuccessfulResponse(200, $payload))->toBe($payload);
});

it('bounds e-conomic curl calls below the PHP request timeout', function (): void {
    $legacyContent = file_get_contents(app_path('modules/economic/economic_m.php'));
    $endpointContent = file_get_contents(app_path('traits/economic_endpoint_t.php'));

    expect($legacyContent)->not->toBeFalse()
        ->and($endpointContent)->not->toBeFalse();

    foreach ([(string)$legacyContent, (string)$endpointContent] as $content) {
        expect($content)->toContain('CURLOPT_CONNECTTIMEOUT => 3')
            ->and($content)->toContain('CURLOPT_TIMEOUT => 30')
            ->and($content)->not->toContain('CURLOPT_TIMEOUT => 0');
    }
});
