/*
   Copyright The containerd Authors.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package integration

import (
	"compress/gzip"
	"context"
	"fmt"
	"net/http"
	"os/exec"
	"runtime"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
	"golang.org/x/mod/semver"

	"github.com/containerd/containerd/v2/pkg/archive"
	"github.com/containerd/containerd/v2/version"
)

// downloadPreviousLatestReleaseBinary downloads the latest version of previous
// release into the target dir.
func downloadPreviousLatestReleaseBinary(t *testing.T, targetDir string) {
	ver := previousReleaseVersion(t)

	downloadReleaseBinary(t, targetDir, ver)
}

// downloadReleaseBinary downloads containerd binary with a given release.
func downloadReleaseBinary(t *testing.T, targetDir string, ver string) {
	targetURL := fmt.Sprintf("https://github.com/containerd/containerd/releases/download/%s/containerd-%s-linux-%s.tar.gz",
		ver, strings.TrimPrefix(ver, "v"), runtime.GOARCH,
	)

	resp, err := http.Get(targetURL) //nolint:gosec
	require.NoError(t, err, "failed to http-get %s", targetURL)

	defer resp.Body.Close()
	require.Equal(t, http.StatusOK, resp.StatusCode)

	tarReader, err := gzip.NewReader(resp.Body)
	require.NoError(t, err, "%s should be gzip stream", targetURL)

	// NOTE: Use native applier to take release tar.gzip stream as first image layer :)
	_, err = archive.Apply(context.Background(), targetDir, tarReader)
	require.NoError(t, err, "failed to unpack %s gzip stream into %s", targetURL, targetDir)
}

// previousReleaseVersion returns the latest version of previous release.
func previousReleaseVersion(t *testing.T) string {
	majorMinor := ctrdPreviousMajorMinor(t)

	tags := gitLsRemoteCtrdTags(t, fmt.Sprintf("refs/tags/%s.*", majorMinor))
	require.True(t, len(tags) >= 1)

	// sort them and get the latest version
	semver.Sort(tags)
	return tags[len(tags)-1]
}

// ctrdPreviousMajorMinor gets the current version of running containerd.
//
// TODO(fuweid): We should parse containerd --version to get the result.
func ctrdPreviousMajorMinor(t *testing.T) string {
	currentVer := "v" + version.Version

	version := semver.MajorMinor(currentVer)
	switch version {
	case "v2.0":
		return "v1.7"
	case "v1.7":
		return "v1.6"
	default:
		t.Fatalf("unexpected containerd version: %s", currentVer)
		panic("unreachable")
	}
}

// gitLsRemoteTags lists containerd tags based on pattern.
func gitLsRemoteCtrdTags(t *testing.T, pattern string) (_tags []string) {
	cmd := exec.Command("git", "ls-remote", "--tags", "--exit-code",
		"https://github.com/containerd/containerd.git", pattern)

	t.Logf("Running %s", cmd.String())

	out, err := cmd.CombinedOutput()
	require.NoError(t, err, "failed to list tags by pattern %s: %s", pattern, string(out))

	// output is like
	//
	// 137288ad010d39ae6ef578fa53bf9b93d1356c3a        refs/tags/v1.6.8
	// 9cd3357b7fd7218e4aec3eae239db1f68a5a6ec6        refs/tags/v1.6.8^{}
	// cec2382030533cf5797d63a4cdb2b255a9c3c7b6        refs/tags/v1.6.9
	// 1c90a442489720eec95342e1789ee8a5e1b9536f        refs/tags/v1.6.9^{}
	refTags := strings.Fields(string(out))
	require.True(t, len(refTags)%2 == 0)

	tags := make([]string, 0, len(refTags)/2)
	for i := 1; i < len(refTags); i += 2 {
		rawTag := refTags[i]
		require.True(t, strings.HasPrefix(rawTag, "refs/tags/"))

		if strings.HasSuffix(rawTag, "^{}") {
			continue
		}
		rawTag = strings.TrimPrefix(rawTag, "refs/tags/")
		tags = append(tags, rawTag)
	}
	return tags
}
