package s3_test

import (
	"encoding/json"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"testing"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/aws/awserr"
	"github.com/aws/aws-sdk-go-v2/aws/endpoints"
	"github.com/aws/aws-sdk-go-v2/internal/awstesting/unit"
	"github.com/aws/aws-sdk-go-v2/service/s3"
)

type s3BucketTest struct {
	bucket  string
	url     string
	errCode string
}

var (
	sslTests = []s3BucketTest{
		{"abc", "https://abc.s3.mock-region.amazonaws.com/", ""},
		{"a$b$c", "https://s3.mock-region.amazonaws.com/a%24b%24c", ""},
		{"a.b.c", "https://s3.mock-region.amazonaws.com/a.b.c", ""},
		{"a..bc", "https://s3.mock-region.amazonaws.com/a..bc", ""},
	}

	nosslTests = []s3BucketTest{
		{"a.b.c", "http://a.b.c.s3.mock-region.amazonaws.com/", ""},
		{"a..bc", "http://s3.mock-region.amazonaws.com/a..bc", ""},
	}

	forcepathTests = []s3BucketTest{
		{"abc", "https://s3.mock-region.amazonaws.com/abc", ""},
		{"a$b$c", "https://s3.mock-region.amazonaws.com/a%24b%24c", ""},
		{"a.b.c", "https://s3.mock-region.amazonaws.com/a.b.c", ""},
		{"a..bc", "https://s3.mock-region.amazonaws.com/a..bc", ""},
	}

	accelerateTests = []s3BucketTest{
		{"abc", "https://abc.s3-accelerate.amazonaws.com/", ""},
		{"a.b.c", "https://s3.mock-region.amazonaws.com/%7BBucket%7D", "InvalidParameterException"},
		{"a$b$c", "https://s3.mock-region.amazonaws.com/%7BBucket%7D", "InvalidParameterException"},
	}

	accelerateNoSSLTests = []s3BucketTest{
		{"abc", "http://abc.s3-accelerate.amazonaws.com/", ""},
		{"a.b.c", "http://a.b.c.s3-accelerate.amazonaws.com/", ""},
		{"a$b$c", "http://s3.mock-region.amazonaws.com/%7BBucket%7D", "InvalidParameterException"},
	}

	accelerateDualstack = []s3BucketTest{
		{"abc", "https://abc.s3-accelerate.dualstack.amazonaws.com/", ""},
		{"a.b.c", "https://s3.dualstack.mock-region.amazonaws.com/%7BBucket%7D", "InvalidParameterException"},
		{"a$b$c", "https://s3.dualstack.mock-region.amazonaws.com/%7BBucket%7D", "InvalidParameterException"},
	}
)

func runTests(t *testing.T, svc *s3.Client, tests []s3BucketTest) {
	t.Helper()

	for i, test := range tests {
		req := svc.ListObjectsRequest(&s3.ListObjectsInput{Bucket: &test.bucket})
		req.Build()

		if test.errCode != "" {
			if err := req.Error; err == nil {
				t.Fatalf("%d, expect error, got none", i)
			}
			if a, e := req.Error.(awserr.Error).Code(), test.errCode; !strings.Contains(a, e) {
				t.Errorf("%d, expect error code to contain %q, got %q", i, e, a)
			}
		} else {
			if err := req.Error; err != nil {
				t.Fatalf("%d, expect no error, got %v", i, err)
			}
			if e, a := test.url, req.HTTPRequest.URL.String(); e != a {
				t.Errorf("%d, expect url %s, got %s", i, e, a)
			}
		}
	}
}

func TestAccelerateBucketBuild(t *testing.T) {
	cfg := unit.Config()
	cfg.EndpointResolver = endpoints.NewDefaultResolver()

	s := s3.New(cfg)
	s.UseAccelerate = true
	runTests(t, s, accelerateTests)
}

func TestAccelerateNoSSLBucketBuild(t *testing.T) {
	cfg := unit.Config()
	resolver := endpoints.NewDefaultResolver()
	resolver.DisableSSL = true
	cfg.EndpointResolver = resolver

	s := s3.New(cfg)
	s.UseAccelerate = true
	runTests(t, s, accelerateNoSSLTests)
}

func TestAccelerateDualstackBucketBuild(t *testing.T) {
	cfg := unit.Config()
	resolver := endpoints.NewDefaultResolver()
	resolver.UseDualStack = true
	cfg.EndpointResolver = resolver

	s := s3.New(cfg)
	s.UseAccelerate = true
	runTests(t, s, accelerateDualstack)
}

func TestHostStyleBucketBuild(t *testing.T) {
	cfg := unit.Config()
	cfg.EndpointResolver = endpoints.NewDefaultResolver()

	s := s3.New(cfg)
	runTests(t, s, sslTests)
}

func TestHostStyleBucketBuildNoSSL(t *testing.T) {
	cfg := unit.Config()
	resolver := endpoints.NewDefaultResolver()
	resolver.DisableSSL = true
	cfg.EndpointResolver = resolver

	s := s3.New(cfg)
	runTests(t, s, nosslTests)
}

func TestPathStyleBucketBuild(t *testing.T) {
	cfg := unit.Config()
	cfg.EndpointResolver = endpoints.NewDefaultResolver()

	s := s3.New(cfg)
	s.ForcePathStyle = true
	runTests(t, s, forcepathTests)
}

func TestHostStyleBucketGetBucketLocation(t *testing.T) {
	cfg := unit.Config()
	cfg.EndpointResolver = endpoints.NewDefaultResolver()

	s := s3.New(cfg)
	req := s.GetBucketLocationRequest(&s3.GetBucketLocationInput{
		Bucket: aws.String("bucket"),
	})

	req.Build()
	if req.Error != nil {
		t.Fatalf("expect no error, got %v", req.Error)
	}
	u, _ := url.Parse(req.HTTPRequest.URL.String())
	if e, a := "bucket", u.Host; strings.Contains(a, e) {
		t.Errorf("expect %s to not be in %s", e, a)
	}
	if e, a := "bucket", u.Path; !strings.Contains(a, e) {
		t.Errorf("expect %s to be in %s", e, a)
	}
}
func TestVirtualHostStyleSuite(t *testing.T) {
	f, err := os.Open(filepath.Join("testdata", "virtual_host.json"))
	if err != nil {
		t.Fatalf("expect no error, %v", err)
	}

	cases := []struct {
		Bucket                    string
		Region                    string
		UseDualStack              bool
		UseS3Accelerate           bool
		ConfiguredAddressingStyle string

		ExpectedURI string
	}{}

	decoder := json.NewDecoder(f)
	if err := decoder.Decode(&cases); err != nil {
		t.Fatalf("expect no error, %v", err)
	}

	const testPathStyle = "path"
	for i, c := range cases {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			cfg := unit.Config()
			resolver := endpoints.NewDefaultResolver()
			resolver.UseDualStack = c.UseDualStack
			cfg.EndpointResolver = resolver
			cfg.Region = c.Region

			svc := s3.New(cfg)
			svc.ForcePathStyle = c.ConfiguredAddressingStyle == testPathStyle
			svc.UseAccelerate = c.UseS3Accelerate

			req := svc.HeadBucketRequest(&s3.HeadBucketInput{
				Bucket: &c.Bucket,
			})
			req.Build()
			if req.Error != nil {
				t.Fatalf("expect no error, got %v", req.Error)
			}

			// Trim trailing '/' that are added by the SDK but not in the tests.
			actualURI := strings.TrimRightFunc(
				req.HTTPRequest.URL.String(),
				func(r rune) bool { return r == '/' },
			)
			if e, a := c.ExpectedURI, actualURI; e != a {
				t.Errorf("expect\n%s\nurl to be\n%s", e, a)
			}
		})
	}
}
