// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package getproviders

import (
	"context"
	"fmt"
	"regexp"
	"strings"
	"testing"

	tfaddr "github.com/opentofu/registry-address"

	"github.com/apparentlymart/go-versions/versions"
	"github.com/google/go-cmp/cmp"
	svchost "github.com/hashicorp/terraform-svchost"

	"github.com/opentofu/opentofu/internal/addrs"
)

func TestSourceAvailableVersions(t *testing.T) {
	source, baseURL, close := testRegistrySource(t)
	defer close()

	tests := []struct {
		provider     string
		wantVersions []string
		wantErr      string
	}{
		// These test cases are relying on behaviors of the fake provider
		// registry server implemented in registry_client_test.go.
		{
			"example.com/awesomesauce/happycloud",
			[]string{"0.1.0", "1.0.0", "1.2.0", "2.0.0"},
			``,
		},
		{
			"example.com/weaksauce/no-versions",
			nil,
			``, // having no versions is not an error, it's just odd
		},
		{
			"example.com/nonexist/nonexist",
			nil,
			`provider registry example.com does not have a provider named example.com/nonexist/nonexist`,
		},
		{
			"not.example.com/foo/bar",
			nil,
			`host not.example.com does not offer a OpenTofu provider registry`,
		},
		{
			"too-new.example.com/foo/bar",
			nil,
			`host too-new.example.com does not support the provider registry protocol required by this OpenTofu version, but may be compatible with a different OpenTofu version`,
		},
		{
			"fails.example.com/foo/bar",
			nil,
			`could not query provider registry for fails.example.com/foo/bar: the request failed after 2 attempts, please try again later: Get "` + baseURL + `/fails-immediately/foo/bar/versions": EOF`,
		},
	}

	for _, test := range tests {
		t.Run(test.provider, func(t *testing.T) {
			provider := addrs.MustParseProviderSourceString(test.provider)
			gotVersions, _, err := source.AvailableVersions(context.Background(), provider)

			if err != nil {
				if test.wantErr == "" {
					t.Fatalf("wrong error\ngot:  %s\nwant: <nil>", err.Error())
				}
				if got, want := err.Error(), test.wantErr; got != want {
					t.Fatalf("wrong error\ngot:  %s\nwant: %s", got, want)
				}
				return
			}

			if test.wantErr != "" {
				t.Fatalf("wrong error\ngot:  <nil>\nwant: %s", test.wantErr)
			}

			var gotVersionsStr []string
			if gotVersions != nil {
				gotVersionsStr = make([]string, len(gotVersions))
				for i, v := range gotVersions {
					gotVersionsStr[i] = v.String()
				}
			}

			if diff := cmp.Diff(test.wantVersions, gotVersionsStr); diff != "" {
				t.Errorf("wrong result\n%s", diff)
			}
		})
	}
}

func TestSourceAvailableVersions_warnings(t *testing.T) {
	source, _, close := testRegistrySource(t)
	defer close()

	provider := addrs.MustParseProviderSourceString("example.com/weaksauce/no-versions")
	_, warnings, err := source.AvailableVersions(context.Background(), provider)
	if err != nil {
		t.Fatalf("unexpected error: %s", err.Error())
	}

	if len(warnings) != 1 {
		t.Fatalf("wrong number of warnings. Expected 1, got %d", len(warnings))
	}

}

func TestSourcePackageMeta(t *testing.T) {
	source, baseURL, close := testRegistrySource(t)
	defer close()

	validMeta := PackageMeta{
		Provider: addrs.NewProvider(
			svchost.Hostname("example.com"), "awesomesauce", "happycloud",
		),
		Version:          versions.MustParseVersion("1.2.0"),
		ProtocolVersions: VersionList{versions.MustParseVersion("5.0.0")},
		TargetPlatform:   Platform{"linux", "amd64"},
		Filename:         "happycloud_1.2.0.zip",
		Location:         PackageHTTPURL(baseURL + "/pkg/awesomesauce/happycloud_1.2.0.zip"),
	}
	validMeta.Authentication = PackageAuthenticationAll(
		NewMatchingChecksumAuthentication(
			[]byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n000000000000000000000000000000000000000000000000000000000000face happycloud_1.2.0_face.zip\n"),
			"happycloud_1.2.0.zip",
			[32]byte{30: 0xf0, 31: 0x0d},
		),
		NewArchiveChecksumAuthentication(Platform{"linux", "amd64"}, [32]byte{30: 0xf0, 31: 0x0d}),
		NewSignatureAuthentication(
			validMeta,
			[]byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n000000000000000000000000000000000000000000000000000000000000face happycloud_1.2.0_face.zip\n"),
			[]byte("GPG signature"),
			[]SigningKey{
				{ASCIIArmor: TestingPublicKey},
			},
			&tfaddr.Provider{Hostname: "example.com", Namespace: "awesomesauce", Type: "happycloud"},
		),
	)

	tests := []struct {
		provider   string
		version    string
		os, arch   string
		want       PackageMeta
		wantHashes []Hash
		wantErr    string
	}{
		// These test cases are relying on behaviors of the fake provider
		// registry server implemented in registry_client_test.go.
		{
			"example.com/awesomesauce/happycloud",
			"1.2.0",
			"linux", "amd64",
			validMeta,
			[]Hash{
				"zh:000000000000000000000000000000000000000000000000000000000000f00d",
				"zh:000000000000000000000000000000000000000000000000000000000000face",
			},
			``,
		},
		{
			"example.com/awesomesauce/happycloud",
			"1.2.0",
			"nonexist", "amd64",
			PackageMeta{},
			nil,
			`provider example.com/awesomesauce/happycloud 1.2.0 is not available for nonexist_amd64`,
		},
		{
			"not.example.com/awesomesauce/happycloud",
			"1.2.0",
			"linux", "amd64",
			PackageMeta{},
			nil,
			`host not.example.com does not offer a OpenTofu provider registry`,
		},
		{
			"too-new.example.com/awesomesauce/happycloud",
			"1.2.0",
			"linux", "amd64",
			PackageMeta{},
			nil,
			`host too-new.example.com does not support the provider registry protocol required by this OpenTofu version, but may be compatible with a different OpenTofu version`,
		},
		{
			"fails.example.com/awesomesauce/happycloud",
			"1.2.0",
			"linux", "amd64",
			PackageMeta{},
			nil,
			`could not query provider registry for fails.example.com/awesomesauce/happycloud: the request failed after 2 attempts, please try again later: Get "http://placeholder-origin/fails-immediately/awesomesauce/happycloud/1.2.0/download/linux/amd64": EOF`,
		},
	}

	// Sometimes error messages contain specific HTTP endpoint URLs, but
	// since our test server is on a random port we'd not be able to
	// consistently match those. Instead, we'll normalize the URLs.
	urlPattern := regexp.MustCompile(`http://[^/]+/`)

	cmpOpts := cmp.Comparer(Version.Same)

	for _, test := range tests {
		t.Run(fmt.Sprintf("%s for %s_%s", test.provider, test.os, test.arch), func(t *testing.T) {
			// TEMP: We don't yet have a function for parsing provider
			// source addresses so we'll just fake it in here for now.
			parts := strings.Split(test.provider, "/")
			providerAddr := addrs.Provider{
				Hostname:  svchost.Hostname(parts[0]),
				Namespace: parts[1],
				Type:      parts[2],
			}

			version := versions.MustParseVersion(test.version)

			got, err := source.PackageMeta(context.Background(), providerAddr, version, Platform{test.os, test.arch})

			if err != nil {
				if test.wantErr == "" {
					t.Fatalf("wrong error\ngot:  %s\nwant: <nil>", err.Error())
				}
				gotErr := urlPattern.ReplaceAllLiteralString(err.Error(), "http://placeholder-origin/")
				if got, want := gotErr, test.wantErr; got != want {
					t.Fatalf("wrong error\ngot:  %s\nwant: %s", got, want)
				}
				return
			}

			if test.wantErr != "" {
				t.Fatalf("wrong error\ngot:  <nil>\nwant: %s", test.wantErr)
			}

			if diff := cmp.Diff(got, test.want, cmpOpts); diff != "" {
				t.Errorf("wrong result\n%s", diff)
			}
			if diff := cmp.Diff(test.wantHashes, got.AcceptableHashes()); diff != "" {
				t.Errorf("wrong AcceptableHashes result\n%s", diff)
			}
		})
	}

}
