<?php

declare(strict_types=1);

namespace OpenSearch\Tests\Handlers;

use Aws\Credentials\CredentialProvider;
use Aws\Credentials\Credentials;
use GuzzleHttp\Ring\Future\CompletedFutureArray;
use OpenSearch\ClientBuilder;
use OpenSearch\Handlers\SigV4Handler;
use PHPUnit\Framework\TestCase;

class SigV4HandlerTest extends TestCase
{
    private const ENV_KEYS_USED = [CredentialProvider::ENV_KEY, CredentialProvider::ENV_SECRET];

    private $envTemp = [];

    protected function setUp(): void
    {
        $this->envTemp = array_combine(self::ENV_KEYS_USED, array_map(
            function ($envVarName) {
                $current = getenv($envVarName);
                putenv($envVarName);
                return $current;
            },
            self::ENV_KEYS_USED
        ));
    }

    protected function tearDown(): void
    {
        foreach ($this->envTemp as $key => $value) {
            putenv("$key=$value");
        }
        $this->envTemp = [];
    }

    public function testSignsRequestsTheSdkDefaultCredentialProviderChain()
    {
        $key = 'foo';
        $toWrap = function (array $ringRequest) use ($key) {
            $this->assertArrayHasKey('X-Amz-Date', $ringRequest['headers']);
            $this->assertArrayHasKey('Authorization', $ringRequest['headers']);
            $this->assertArrayHasKey('x-amz-content-sha256', $ringRequest['headers']);
            $this->assertMatchesRegularExpression(
                "~^AWS4-HMAC-SHA256 Credential=$key/\\d{8}/us-west-2/es/aws4_request~",
                $ringRequest['headers']['Authorization'][0]
            );

            return $this->getGenericResponse();
        };
        putenv(CredentialProvider::ENV_KEY . "=$key");
        putenv(CredentialProvider::ENV_SECRET . '=bar');
        $client = ClientBuilder::create()
            ->setHandler($toWrap)
            ->setSigV4Region('us-west-2')
            ->setSigV4CredentialProvider(true)
            ->build();

        $client->search([
            'index' => 'index',
            'body' => [
                'query' => [ 'match_all' => (object)[] ],
            ],
        ]);
    }

    public function testSignsWithProvidedCredentials()
    {
        $toWrap = function (array $ringRequest) {
            $this->assertArrayHasKey('X-Amz-Security-Token', $ringRequest['headers']);
            $this->assertSame('baz', $ringRequest['headers']['X-Amz-Security-Token'][0]);
            $this->assertArrayHasKey('x-amz-content-sha256', $ringRequest['headers']);
            $this->assertMatchesRegularExpression(
                '~^AWS4-HMAC-SHA256 Credential=foo/\d{8}/us-west-2/es/aws4_request~',
                $ringRequest['headers']['Authorization'][0]
            );

            return $this->getGenericResponse();
        };

        $client = ClientBuilder::create()
            ->setHandler($toWrap)
            ->setSigV4Region('us-west-2')
            ->setSigV4CredentialProvider(new Credentials('foo', 'bar', 'baz'))
            ->build();

        $client->search([
            'index' => 'index',
            'body' => [
                'query' => [ 'match_all' => (object)[] ],
            ],
        ]);
    }

    public function testSignsWithProvidedCredentialsAndService()
    {
        $toWrap = function (array $ringRequest) {
            $this->assertArrayHasKey('X-Amz-Security-Token', $ringRequest['headers']);
            $this->assertSame('baz', $ringRequest['headers']['X-Amz-Security-Token'][0]);
            $this->assertArrayHasKey('x-amz-content-sha256', $ringRequest['headers']);
            $this->assertMatchesRegularExpression(
                '~^AWS4-HMAC-SHA256 Credential=foo/\d{8}/us-west-2/aoss/aws4_request~',
                $ringRequest['headers']['Authorization'][0]
            );

            return $this->getGenericResponse();
        };

        $client = ClientBuilder::create()
            ->setHandler($toWrap)
            ->setSigV4Region('us-west-2')
            ->setSigV4Service('aoss')
            ->setSigV4CredentialProvider(new Credentials('foo', 'bar', 'baz'))
            ->build();

        $client->search([
            'index' => 'index',
            'body' => [
                'query' => ['match_all' => (object)[]],
            ],
        ]);
    }

    public function testEmptyBodyProducesCorrectSha256()
    {
        $toWrap = function (array $ringRequest) {
            $this->assertArrayHasKey('X-Amz-Security-Token', $ringRequest['headers']);
            $this->assertSame('baz', $ringRequest['headers']['X-Amz-Security-Token'][0]);
            $this->assertArrayHasKey('x-amz-content-sha256', $ringRequest['headers']);
            $this->assertSame($ringRequest['headers']['x-amz-content-sha256'][0], 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
            $this->assertMatchesRegularExpression(
                '~^AWS4-HMAC-SHA256 Credential=foo/\d{8}/us-west-2/aoss/aws4_request~',
                $ringRequest['headers']['Authorization'][0]
            );

            return $this->getGenericResponse();
        };

        $client = ClientBuilder::create()
            ->setHandler($toWrap)
            ->setSigV4Region('us-west-2')
            ->setSigV4Service('aoss')
            ->setSigV4CredentialProvider(new Credentials('foo', 'bar', 'baz'))
            ->build();

        $client->indices()->exists(['index' => 'index']);
    }

    public function testEmptyRequestBodiesShouldBeNull()
    {
        $toWrap = function (array $ringRequest) {
            $this->assertNull($ringRequest['body']);

            return $this->getGenericResponse();
        };

        $client = ClientBuilder::create()
            ->setHandler($toWrap)
            ->setSigV4Region('us-west-2')
            ->setSigV4CredentialProvider(new Credentials('foo', 'bar', 'baz'))
            ->build();

        $client->indices()->exists(['index' => 'index']);
    }

    public function testNonEmptyRequestBodiesShouldNotBeNull()
    {
        $toWrap = function (array $ringRequest) {
            $this->assertNotNull($ringRequest['body']);

            return $this->getGenericResponse();
        };

        $client = ClientBuilder::create()
            ->setHandler($toWrap)
            ->setSigV4Region('us-west-2')
            ->setSigV4CredentialProvider(new Credentials('foo', 'bar', 'baz'))
            ->build();

        $client->search([
            'index' => 'index',
            'body' => [
                'query' => [ 'match_all' => (object)[] ],
            ],
        ]);
    }

    public function testClientParametersShouldBePassedToHandler()
    {
        $toWrap = function (array $ringRequest) {
            $this->assertArrayHasKey('client', $ringRequest);
            $this->assertArrayHasKey('timeout', $ringRequest['client']);
            $this->assertArrayHasKey('connect_timeout', $ringRequest['client']);

            return $this->getGenericResponse();
        };

        $client = ClientBuilder::create()
            ->setHandler($toWrap)
            ->setSigV4Region('us-west-2')
            ->setSigV4CredentialProvider(new Credentials('foo', 'bar', 'baz'))
            ->setConnectionParams(['client' => ['timeout' => 5, 'connect_timeout' => 5]])
            ->build();

        $client->indices()->exists(['index' => 'index']);
    }

    public function testClientPortDeterminedByURL()
    {
        $toWrap = function (array $ringRequest) {
            $this->assertArrayNotHasKey(CURLOPT_PORT, $ringRequest['client']['curl']);

            return $this->getGenericResponse();
        };

        $client = ClientBuilder::create()
            ->setHandler($toWrap)
            ->setHosts(['https://search--hgkaewb2ytci3t3y6yghh5m5vje.eu-central-1.es.amazonaws.com'])
            ->setSigV4Region('us-west-2')
            ->setSigV4CredentialProvider(new Credentials('foo', 'bar', 'baz'))
            ->build();

        $client->indices()->exists(['index' => 'index']);
    }

    private function getGenericResponse(): CompletedFutureArray
    {
        return new CompletedFutureArray([
            'status' => 200,
            'body' => fopen('php://memory', 'r'),
            'transfer_stats' => ['total_time' => 0],
            'effective_url' => 'https://www.example.com',
        ]);
    }
}