// Copyright 2017 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package option

import (
	"testing"

	"crypto/tls"
	"math/big"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/internal"
	"google.golang.org/grpc"
)

// Below is a dummy certificate/key pair taken from
// https://golang.org/src/crypto/tls/tls_test.go
const certPEM = `-----BEGIN CERTIFICATE-----
MIIB0zCCAX2gAwIBAgIJAI/M7BYjwB+uMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTIwOTEyMjE1MjAyWhcNMTUwOTEyMjE1MjAyWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANLJ
hPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wok/4xIA+ui35/MmNa
rtNuC+BdZ1tMuVCPFZcCAwEAAaNQME4wHQYDVR0OBBYEFJvKs8RfJaXTH08W+SGv
zQyKn0H8MB8GA1UdIwQYMBaAFJvKs8RfJaXTH08W+SGvzQyKn0H8MAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQEFBQADQQBJlffJHybjDGxRMqaRmDhX0+6v02TUKZsW
r5QuVbpQhH6u+0UgcW0jp9QwpxoPTLTWGXEWBBBurxFwiCBhkQ+V
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo
k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G
6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N
MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW
SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T
xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi
D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g==
-----END PRIVATE KEY-----
`

// Check that the slice passed into WithScopes is copied.
func TestCopyScopes(t *testing.T) {
	o := &internal.DialSettings{}

	scopes := []string{"a", "b"}
	WithScopes(scopes...).Apply(o)

	// Modify after using.
	scopes[1] = "c"

	if o.Scopes[0] != "a" || o.Scopes[1] != "b" {
		t.Errorf("want ['a', 'b'], got %+v", o.Scopes)
	}
}

func TestApply(t *testing.T) {
	conn := &grpc.ClientConn{}
	opts := []ClientOption{
		WithEndpoint("https://example.com:443"),
		WithScopes("a"), // the next WithScopes should overwrite this one
		WithScopes("https://example.com/auth/helloworld", "https://example.com/auth/otherthing"),
		WithGRPCConn(conn),
		WithUserAgent("ua"),
		WithAuthCredentialsFile(ServiceAccount, "service-account.json"),
		WithCredentialsFile("unknown-type.json"),
		WithAuthCredentialsJSON(ServiceAccount, []byte(`{some: "json", type: "service_account"}`)),
		WithCredentialsJSON([]byte(`{some: "json"}`)),
		WithCredentials(&google.DefaultCredentials{ProjectID: "p"}),
		WithAPIKey("api-key"),
		WithAudiences("https://example.com/"),
		WithQuotaProject("user-project"),
		WithRequestReason("Request Reason"),
		WithTelemetryDisabled(),
		WithUniverseDomain("universe.com"),
	}
	var got internal.DialSettings
	for _, opt := range opts {
		opt.Apply(&got)
	}
	want := internal.DialSettings{
		Scopes:              []string{"https://example.com/auth/helloworld", "https://example.com/auth/otherthing"},
		UserAgent:           "ua",
		Endpoint:            "https://example.com:443",
		GRPCConn:            conn,
		Credentials:         &google.DefaultCredentials{ProjectID: "p"},
		AuthCredentialsFile: "service-account.json",
		AuthCredentialsType: ServiceAccount,
		CredentialsFile:     "unknown-type.json",
		AuthCredentialsJSON: []byte(`{some: "json", type: "service_account"}`),
		CredentialsJSON:     []byte(`{some: "json"}`),
		APIKey:              "api-key",
		Audiences:           []string{"https://example.com/"},
		QuotaProject:        "user-project",
		RequestReason:       "Request Reason",
		TelemetryDisabled:   true,
		UniverseDomain:      "universe.com",
	}
	ignore := []cmp.Option{
		cmpopts.IgnoreUnexported(grpc.ClientConn{}),
		cmpopts.IgnoreFields(google.Credentials{}, "udMu", "universeDomain"),
	}
	if !cmp.Equal(got, want, ignore...) {
		t.Error(cmp.Diff(got, want, ignore...))
	}
}

func mockClientCertSource(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
	cert, _ := tls.X509KeyPair([]byte(certPEM), []byte(certPEM))
	return &cert, nil
}

func TestApplyClientCertSource(t *testing.T) {
	opts := []ClientOption{
		WithClientCertSource(mockClientCertSource),
	}
	var got internal.DialSettings
	for _, opt := range opts {
		opt.Apply(&got)
	}
	want := internal.DialSettings{
		ClientCertSource: mockClientCertSource,
	}

	// Functions cannot be compared in Golang for equality, so we will compare the output of the functions instead.
	certGot, err := got.ClientCertSource(nil)
	if err != nil {
		t.Error(err)
	}
	certWant, err := want.ClientCertSource(nil)
	if err != nil {
		t.Error(err)
	}
	if !cmp.Equal(certGot, certWant, cmpopts.IgnoreUnexported(big.Int{}), cmpopts.IgnoreFields(tls.Certificate{}, "Leaf")) {
		t.Error(cmp.Diff(certGot, certWant, cmpopts.IgnoreUnexported(big.Int{}), cmpopts.IgnoreFields(tls.Certificate{}, "Leaf")))
	}
}
