<?php
namespace Aws\Test\S3;

use Aws\Command;
use Aws\CommandInterface;
use Aws\Exception\InvalidRegionException;
use Aws\Exception\UnresolvedEndpointException;
use Aws\Middleware;
use Aws\Result;
use Aws\S3\Exception\S3Exception;
use Aws\S3\S3Client;
use Aws\Test\UsesServiceTrait;
use GuzzleHttp\Promise;
use Yoast\PHPUnitPolyfills\TestCases\TestCase;
use Psr\Http\Message\RequestInterface;

/**
 * @covers \Aws\S3\BucketEndpointMiddleware
 */
class BucketEndpointArnMiddlewareTest extends TestCase
{
    use UsesServiceTrait;

    /**
     * @dataProvider accessPointArnCases
     *
     * @param $arn
     * @param $options
     * @param $endpoint
     * @param $key
     * @param $signingRegion
     * @param $signingService
     * @throws \Exception
     */
    public function testCorrectlyModifiesUri(
        $arn,
        $options,
        $endpoint,
        $key,
        $signingRegion,
        $signingService
    ) {
        $s3 = $this->getTestClient('s3', $options);
        $this->addMockResults($s3, [[]]);
        $command = $s3->getCommand(
            'GetObject',
            [
                'Bucket' => $arn,
                'Key' => $key
            ]
        );

        $command->getHandlerList()->appendSign(
            Middleware::tap(function (
                CommandInterface $cmd,
                RequestInterface $req
            ) use ($endpoint, $key, $signingRegion, $signingService) {
                $this->assertEquals(
                    $endpoint,
                    $req->getUri()->getHost()
                );
                $this->assertSame("/{$key}", $req->getRequestTarget());
                if (isset($cmd['@context']['signing_region']) && $signingRegion) {
                    $this->assertEquals(
                        $signingRegion,
                        $cmd['@context']['signing_region']
                    );
                }
                if (!empty($signingService) && isset($cmd['@context']['signing_service'])) {
                    $this->assertEquals(
                        $signingService,
                        $cmd['@context']['signing_service']
                    );
                }

                if ($signingRegion) {
                    $this->assertStringContainsString(
                        "/{$signingRegion}/s3",
                        $req->getHeader('Authorization')[0]
                    );
                }
            })
        );
        $s3->execute($command);
    }

    public function accessPointArnCases()
    {
        return [
            // Standard case
            [
                'arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint',
                [
                    'region' => 'us-west-2',
                ],
                'myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com',
                'Bar/Baz',
                'us-west-2',
                null,
            ],
            // Different regions, use_arn_region true
            [
                'arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint',
                [
                    'region' => 'us-west-2',
                    'use_arn_region' => true,
                ],
                'myendpoint-123456789012.s3-accesspoint.us-east-1.amazonaws.com',
                'Bar/Baz',
                'us-east-1',
                null,
            ],
            // s3-external, use_arn_region true
            [
                'arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint',
                [
                    'region' => 's3-external-1',
                    'use_arn_region' => true,
                ],
                'myendpoint-123456789012.s3-accesspoint.us-east-1.amazonaws.com',
                'Bar/Baz',
                'us-east-1',
                null,
            ],
            // With dual-stack endpoint
            [
                'arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint',
                [
                    'region' => 'us-west-2',
                    'use_dual_stack_endpoint' => true,
                ],
                'myendpoint-123456789012.s3-accesspoint.dualstack.us-west-2.amazonaws.com',
                'Bar/Baz',
                'us-west-2',
                null,
            ],
            // With dual-stack fips endpoint, use_arn_region true
            [
                'arn:aws-us-gov:s3:us-gov-east-1:123456789012:accesspoint:myendpoint',
                [
                    'region' => 'fips-us-gov-east-1',
                    'use_dual_stack_endpoint' => true,
                    'use_arn_region' => true,
                ],
                'myendpoint-123456789012.s3-accesspoint-fips.dualstack.us-gov-east-1.amazonaws.com',
                'Bar/Baz',
                'us-gov-east-1',
                null,
            ],
            // Non-aws partition, use_arn_region true
            [
                'arn:aws-cn:s3:cn-north-1:123456789012:accesspoint:myendpoint',
                [
                    'region' => 'cn-north-1',
                    'use_arn_region' => true,
                ],
                'myendpoint-123456789012.s3-accesspoint.cn-north-1.amazonaws.com.cn',
                'Bar/Baz',
                'cn-north-1',
                null,
            ],
            // Non-aws partition, use_arn_region false
            [
                'arn:aws-cn:s3:cn-north-1:123456789012:accesspoint:myendpoint',
                [
                    'region' => 'cn-north-1',
                    'use_arn_region' => false,
                ],
                'myendpoint-123456789012.s3-accesspoint.cn-north-1.amazonaws.com.cn',
                'Bar/Baz',
                'cn-north-1',
                null,
            ],
            // Non-aws partition, differing regions
            [
                'arn:aws-cn:s3:cn-northwest-1:123456789012:accesspoint:myendpoint',
                [
                    'region' => 'cn-north-1',
                    'use_arn_region' => true,
                ],
                'myendpoint-123456789012.s3-accesspoint.cn-northwest-1.amazonaws.com.cn',
                'Bar/Baz',
                'cn-northwest-1',
                null,
            ],
            // Gov region, use_arn_region true
            [
                'arn:aws-us-gov:s3:us-gov-east-1:123456789012:accesspoint:myendpoint',
                [
                    'region' => 'us-gov-east-1',
                    'use_arn_region' => true,
                ],
                'myendpoint-123456789012.s3-accesspoint.us-gov-east-1.amazonaws.com',
                'Bar/Baz',
                'us-gov-east-1',
                null,
            ],
            // Fips region, use_arn_region true
            [
                'arn:aws-us-gov:s3:us-gov-east-1:123456789012:accesspoint:myendpoint',
                [
                    'region' => 'fips-us-gov-east-1',
                    'use_arn_region' => true,
                ],
                'myendpoint-123456789012.s3-accesspoint-fips.us-gov-east-1.amazonaws.com',
                'Bar/Baz',
                'us-gov-east-1',
                null,
            ],
            // Fips region with dualstack
            [
                'arn:aws-us-gov:s3:us-gov-east-1:123456789012:accesspoint:myendpoint',
                [
                    'region' => 'fips-us-gov-east-1',
                    'use_arn_region' => true,
                    'use_dual_stack_endpoint' => true,
                ],
                'myendpoint-123456789012.s3-accesspoint-fips.dualstack.us-gov-east-1.amazonaws.com',
                'Bar/Baz',
                'us-gov-east-1',
                null,
            ],
            // Fips region, use arn region
            [
                'arn:aws-us-gov:s3:us-gov-west-1:123456789012:accesspoint:myendpoint',
                [
                    'region' => 'fips-us-gov-east-1',
                    'use_arn_region' => true,
                ],
                'myendpoint-123456789012.s3-accesspoint-fips.us-gov-west-1.amazonaws.com',
                'Bar/Baz',
                'us-gov-west-1',
                null,
            ],
            // Fips region with dualstack and use fips region
            [
                'arn:aws-us-gov:s3:us-gov-east-1:123456789012:accesspoint:myendpoint',
                [
                    'region' => 'us-gov-east-1',
                    'use_fips_endpoint' => true,
                ],
                'myendpoint-123456789012.s3-accesspoint-fips.us-gov-east-1.amazonaws.com',
                'Bar/Baz',
                'us-gov-east-1',
                null,
            ],
            // Fips region with dualstack and use fips region and use arn region
            [
                'arn:aws-us-gov:s3:us-gov-west-1:123456789012:accesspoint:myendpoint',
                [
                    'region' => 'us-gov-east-1',
                    'use_arn_region' => true,
                    'use_fips_endpoint' => true,
                ],
                'myendpoint-123456789012.s3-accesspoint-fips.us-gov-west-1.amazonaws.com',
                'Bar/Baz',
                'us-gov-west-1',
                null,
            ],
            // S3 outposts, standard case
            [
                'arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint',
                [
                    'region' => 'us-west-2',
                ],
                'myaccesspoint-123456789012.op-01234567890123456.s3-outposts.us-west-2.amazonaws.com',
                'Bar/Baz',
                null,
                's3-outposts',
            ],
            // S3 outposts, differing regions, use_arn_region true
            [
                'arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint',
                [
                    'region' => 'us-west-2',
                    'use_arn_region' => true,
                ],
                'myaccesspoint-123456789012.op-01234567890123456.s3-outposts.us-east-1.amazonaws.com',
                'Bar/Baz',
                null,
                's3-outposts',
            ],
            // S3 outposts, arn with slashes
            [
                'arn:aws:s3-outposts:us-west-2:123456789012:outpost/op-01234567890123456/accesspoint/myaccesspoint',
                [
                    'region' => 'us-west-2',
                ],
                'myaccesspoint-123456789012.op-01234567890123456.s3-outposts.us-west-2.amazonaws.com',
                'Bar/Baz',
                null,
                's3-outposts',
            ],
            // S3 outposts, us-gov region, use_arn_region true
            [
                'arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint',
                [
                    'region' => 'us-gov-east-1',
                    'use_arn_region' => true,
                ],
                'myaccesspoint-123456789012.op-01234567890123456.s3-outposts.us-gov-east-1.amazonaws.com',
                'Bar/Baz',
                null,
                's3-outposts',
            ],
            // S3 Outposts, non-aws partition
            [
                'arn:aws-cn:s3-outposts:cn-north-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint',
                [
                    'region' => 'cn-north-1',
                ],
                'myaccesspoint-123456789012.op-01234567890123456.s3-outposts.cn-north-1.amazonaws.com.cn',
                'Bar/Baz',
                null,
                's3-outposts',
            ],
            // S3 Outposts, non-aws partition, differing regions, use_arn_region true
            [
                'arn:aws-cn:s3-outposts:cn-northwest-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint',
                [
                    'region' => 'cn-north-1',
                    'use_arn_region' => true,
                ],
                'myaccesspoint-123456789012.op-01234567890123456.s3-outposts.cn-northwest-1.amazonaws.com.cn',
                'Bar/Baz',
                null,
                's3-outposts',
            ],
        ];
    }

    /**
     * @dataProvider incorrectUsageProvider
     *
     * @param CommandInterface $command
     * @param array $config
     * @param \Exception $expected
     */
    public function testThrowsForIncorrectArnUsage($command, $config, \Exception $expected)
    {
        try {
            $s3 = $this->getTestClient('s3', $config);
            $this->addMockResults($s3, [[]]);
            $command = $s3->getCommand(
                $command->getName(),
                $command->toArray()
            );
            $s3->execute($command);
            $this->fail('This was expected to fail with: ' . $expected->getMessage());
        } catch (\Exception $e) {
            $this->assertTrue($e instanceof $expected);
            $this->assertSame(
                $expected->getMessage(),
                $e->getMessage()
            );
        }
    }

    public function incorrectUsageProvider()
    {
        return [
            [
                new Command(
                    'GetObject',
                    [
                        'Bucket' => 'arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint',
                        'Key' => 'Bar/Baz',
                    ]
                ),
                [
                    'region' => 'us-west-2',
                    'use_accelerate_endpoint' => true,
                ],
                new UnresolvedEndpointException(
            'Access Points do not support S3 Accelerate'
                )
            ],
            // Path-style with access point ARN
            [
                new Command(
                    'GetObject',
                    [
                        'Bucket' => 'arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint',
                        'Key' => 'Bar/Baz',
                    ]
                ),
                [
                    'region' => 'us-west-2',
                    'use_path_style_endpoint' => true,
                ],
                new UnresolvedEndpointException(
                    'Path-style addressing cannot be used with ARN buckets'
                )
            ],
            // Wrong ARN type
            [
                new Command(
                    'GetObject',
                    [
                        'Bucket' => 'arn:aws:s3:us-east-1:123456789012:some_type:myendpoint',
                        'Key' => 'Bar/Baz',
                    ]
                ),
                ['region' => 'us-west-2'],
                new UnresolvedEndpointException(
                    'Invalid ARN: Unrecognized format: arn:aws:s3:us-east-1:123456789012:some_type:myendpoint (type: some_type)'
                )
            ],
            // Invalid ARN
            [
                new Command(
                    'GetObject',
                    [
                        'Bucket' => 'arn:not:valid',
                        'Key' => 'Bar/Baz',
                    ]
                ),
                ['region' => 'us-west-2'],
                new UnresolvedEndpointException('Invalid ARN: `arn:not:valid` was not a valid ARN')
            ],
            // Non-matching partition
            [
                new Command(
                    'GetObject',
                    [
                        'Bucket' => 'arn:aws-cn:s3:cn-north-1:123456789012:accesspoint:myendpoint',
                        'Key' => 'Bar/Baz',
                    ]
                ),
                [
                    'region' => 'us-west-2',
                    's3_use_arn_region' => true,
                ],
                new UnresolvedEndpointException(
                    'Client was configured for partition `aws` but ARN (`arn:aws-cn:s3:cn-north-1:123456789012:accesspoint:myendpoint`) has `aws-cn`'
                )
            ],
            // Non-matching calculated partition
            [
                new Command(
                    'GetObject',
                    [
                        'Bucket' => 'arn:aws:s3:cn-north-1:123456789012:accesspoint:myendpoint',
                        'Key' => 'Bar/Baz',
                    ]
                ),
                [
                    'region' => 'us-east-1',
                    's3_use_arn_region' => true,
                ],
                new UnresolvedEndpointException(
                    'Client was configured for partition `aws` but ARN (`arn:aws:s3:cn-north-1:123456789012:accesspoint:myendpoint`) has `aws-cn`'
                )
            ],
            // S3 Outposts, non-matching partition
            [
                new Command(
                    'GetObject',
                    [
                        'Bucket' => 'arn:aws-cn:s3-outposts:cn-north-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint',
                        'Key' => 'Bar/Baz',
                    ]
                ),
                [
                    'region' => 'us-west-2',
                    's3_use_arn_region' => true,
                ],
                new UnresolvedEndpointException(
                    'Client was configured for partition `aws` but ARN (`arn:aws-cn:s3-outposts:cn-north-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint`) has `aws-cn`'
                )
            ],
            // S3 Outposts, dualstack
            [
                new Command(
                    'GetObject',
                    [
                        'Bucket' => 'arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint',
                        'Key' => 'Bar/Baz',
                    ]
                ),
                [
                    'region' => 'us-west-2',
                    'use_dual_stack_endpoint' => true,
                ],
                new UnresolvedEndpointException(
                    'S3 Outposts does not support Dual-stack'
                )
            ],
            // S3 Outposts, accelerate
            [
                new Command(
                    'GetObject',
                    [
                        'Bucket' => 'arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint',
                        'Key' => 'Bar/Baz',
                    ]
                ),
                [
                    'region' => 'us-west-2',
                    'use_accelerate_endpoint' => true,
                ],
                new UnresolvedEndpointException(
        'S3 Outposts does not support S3 Accelerate'                )
            ],
            // s3-external, use_arn_region false
            [
                new Command(
                    'GetObject',
                    [
                        'Bucket' => 'arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint',
                        'Key' => 'Bar/Baz',
                    ]
                ),
                [
                    'region' => 's3-external-1',
                    'use_arn_region' => false,
                ],
                new UnresolvedEndpointException(
                    'Invalid configuration: region from ARN `us-east-1` does not match client region `s3-external-1` and UseArnRegion is `false`'
                )
            ],
            // aws-global, use_arn_region false
            [
                new Command(
                    'GetObject',
                    [
                        'Bucket' => 'arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint',
                        'Key' => 'Bar/Baz',
                    ]
                ),
                [
                    'region' => 'aws-global',
                    'use_arn_region' => false,
                ],
                new UnresolvedEndpointException(
                    'Invalid configuration: region from ARN `us-east-1` does not match client region `aws-global` and UseArnRegion is `false`'
                )
            ],
            // S3 Outposts, invalid ARN
            [
                new Command(
                    'GetObject',
                    [
                        'Bucket' => 'arn:aws:s3-outposts:us-west-2:123456789012:outpost',
                        'Key' => 'Bar/Baz',
                    ]
                ),
                ['region' => 'us-west-2'],
                new UnresolvedEndpointException(
                    'Invalid ARN: The Outpost Id was not set'
                )
            ],
        ];
    }

    public function testCorrectlyHandlesCopyObject()
    {
        $s3 = new S3Client([
            'version' => 'latest',
            'region' => 'us-west-2',
            's3_use_arn_region' => true,
            'handler' => function(CommandInterface $cmd, RequestInterface $req) {
                $this->assertSame(
                    'myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com',
                    $req->getUri()->getHost()
                );
                $this->assertSame(
                    'arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint/myobject',
                    $req->getHeader('x-amz-copy-source')[0]
                );
                return Promise\Create::promiseFor(new Result([]));
            }
        ]);
        $command = $s3->getCommand(
            'CopyObject',
            [
                'Bucket' => 'arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint',
                'CopySource' => 'arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint/myobject',
                'Key' => 'mykey'
            ]
        );
        $s3->execute($command);
    }
}
