// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package integration

import (
	"archive/tar"
	"archive/zip"
	"bytes"
	"compress/gzip"
	"fmt"
	"net/http"
	"testing"

	"forgejo.org/models/db"
	"forgejo.org/models/packages"
	"forgejo.org/models/unittest"
	user_model "forgejo.org/models/user"
	cran_module "forgejo.org/modules/packages/cran"
	"forgejo.org/tests"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestPackageCran(t *testing.T) {
	defer tests.PrepareTestEnv(t)()

	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})

	packageName := "test.package"
	packageVersion := "1.0.3"
	packageAuthor := "KN4CK3R"
	packageDescription := "Gitea Test Package"

	createDescription := func(name, version string) []byte {
		var buf bytes.Buffer
		fmt.Fprintln(&buf, "Package:", name)
		fmt.Fprintln(&buf, "Version:", version)
		fmt.Fprintln(&buf, "Description:", packageDescription)
		fmt.Fprintln(&buf, "Imports: abc,\n123")
		fmt.Fprintln(&buf, "NeedsCompilation: yes")
		fmt.Fprintln(&buf, "License: MIT")
		fmt.Fprintln(&buf, "Author:", packageAuthor)
		return buf.Bytes()
	}

	url := fmt.Sprintf("/api/packages/%s/cran", user.Name)

	t.Run("Source", func(t *testing.T) {
		createArchive := func(filename string, content []byte) *bytes.Buffer {
			var buf bytes.Buffer
			gw := gzip.NewWriter(&buf)
			tw := tar.NewWriter(gw)
			hdr := &tar.Header{
				Name: filename,
				Mode: 0o600,
				Size: int64(len(content)),
			}
			tw.WriteHeader(hdr)
			tw.Write(content)
			tw.Close()
			gw.Close()
			return &buf
		}

		t.Run("Upload", func(t *testing.T) {
			defer tests.PrintCurrentTest(t)()

			uploadURL := url + "/src"

			req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{}))
			MakeRequest(t, req, http.StatusUnauthorized)

			req = NewRequestWithBody(t, "PUT", uploadURL, createArchive(
				"dummy.txt",
				[]byte{},
			)).AddBasicAuth(user.Name)
			MakeRequest(t, req, http.StatusBadRequest)

			req = NewRequestWithBody(t, "PUT", uploadURL, createArchive(
				"package/DESCRIPTION",
				createDescription(packageName, packageVersion),
			)).AddBasicAuth(user.Name)
			MakeRequest(t, req, http.StatusCreated)

			pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeCran)
			require.NoError(t, err)
			assert.Len(t, pvs, 1)

			pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
			require.NoError(t, err)
			assert.Nil(t, pd.SemVer)
			assert.IsType(t, &cran_module.Metadata{}, pd.Metadata)
			assert.Equal(t, packageName, pd.Package.Name)
			assert.Equal(t, packageVersion, pd.Version.Version)

			pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
			require.NoError(t, err)
			assert.Len(t, pfs, 1)
			assert.Equal(t, fmt.Sprintf("%s_%s.tar.gz", packageName, packageVersion), pfs[0].Name)
			assert.True(t, pfs[0].IsLead)

			req = NewRequestWithBody(t, "PUT", uploadURL, createArchive(
				"package/DESCRIPTION",
				createDescription(packageName, packageVersion),
			)).AddBasicAuth(user.Name)
			MakeRequest(t, req, http.StatusConflict)
		})

		t.Run("Download", func(t *testing.T) {
			defer tests.PrintCurrentTest(t)()

			req := NewRequest(t, "GET", fmt.Sprintf("%s/src/contrib/%s_%s.tar.gz", url, packageName, packageVersion)).
				AddBasicAuth(user.Name)
			MakeRequest(t, req, http.StatusOK)
		})

		t.Run("DownloadArchived", func(t *testing.T) {
			defer tests.PrintCurrentTest(t)()

			req := NewRequest(t, "GET", fmt.Sprintf("%s/src/contrib/Archive/%s/%s_%s.tar.gz", url, packageName, packageName, packageVersion)).
				AddBasicAuth(user.Name)
			MakeRequest(t, req, http.StatusOK)
		})

		t.Run("Enumerate", func(t *testing.T) {
			defer tests.PrintCurrentTest(t)()

			req := NewRequest(t, "GET", url+"/src/contrib/PACKAGES").
				AddBasicAuth(user.Name)
			resp := MakeRequest(t, req, http.StatusOK)

			assert.Contains(t, resp.Header().Get("Content-Type"), "text/plain")

			body := resp.Body.String()
			assert.Contains(t, body, fmt.Sprintf("Package: %s", packageName))
			assert.Contains(t, body, fmt.Sprintf("Version: %s", packageVersion))

			req = NewRequest(t, "GET", url+"/src/contrib/PACKAGES.gz").
				AddBasicAuth(user.Name)
			resp = MakeRequest(t, req, http.StatusOK)

			assert.Contains(t, resp.Header().Get("Content-Type"), "application/x-gzip")
		})
	})

	t.Run("Binary", func(t *testing.T) {
		createArchive := func(filename string, content []byte) *bytes.Buffer {
			var buf bytes.Buffer
			archive := zip.NewWriter(&buf)
			w, _ := archive.Create(filename)
			w.Write(content)
			archive.Close()
			return &buf
		}

		t.Run("Upload", func(t *testing.T) {
			defer tests.PrintCurrentTest(t)()

			uploadURL := url + "/bin"

			req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{}))
			MakeRequest(t, req, http.StatusUnauthorized)

			req = NewRequestWithBody(t, "PUT", uploadURL, createArchive(
				"dummy.txt",
				[]byte{},
			)).AddBasicAuth(user.Name)
			MakeRequest(t, req, http.StatusBadRequest)

			req = NewRequestWithBody(t, "PUT", uploadURL+"?platform=&rversion=", createArchive(
				"package/DESCRIPTION",
				createDescription(packageName, packageVersion),
			)).AddBasicAuth(user.Name)
			MakeRequest(t, req, http.StatusBadRequest)

			uploadURL += "?platform=windows&rversion=4.2"

			req = NewRequestWithBody(t, "PUT", uploadURL, createArchive(
				"package/DESCRIPTION",
				createDescription(packageName, packageVersion),
			)).AddBasicAuth(user.Name)
			MakeRequest(t, req, http.StatusCreated)

			pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeCran)
			require.NoError(t, err)
			assert.Len(t, pvs, 1)

			pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
			require.NoError(t, err)
			assert.Len(t, pfs, 2)

			req = NewRequestWithBody(t, "PUT", uploadURL, createArchive(
				"package/DESCRIPTION",
				createDescription(packageName, packageVersion),
			)).AddBasicAuth(user.Name)
			MakeRequest(t, req, http.StatusConflict)
		})

		t.Run("Download", func(t *testing.T) {
			defer tests.PrintCurrentTest(t)()

			cases := []struct {
				Platform       string
				RVersion       string
				ExpectedStatus int
			}{
				{"osx", "4.2", http.StatusNotFound},
				{"windows", "4.1", http.StatusNotFound},
				{"windows", "4.2", http.StatusOK},
			}

			for _, c := range cases {
				req := NewRequest(t, "GET", fmt.Sprintf("%s/bin/%s/contrib/%s/%s_%s.zip", url, c.Platform, c.RVersion, packageName, packageVersion)).
					AddBasicAuth(user.Name)
				MakeRequest(t, req, c.ExpectedStatus)
			}
		})

		t.Run("Enumerate", func(t *testing.T) {
			defer tests.PrintCurrentTest(t)()

			req := NewRequest(t, "GET", url+"/bin/windows/contrib/4.1/PACKAGES")
			MakeRequest(t, req, http.StatusNotFound)

			req = NewRequest(t, "GET", url+"/bin/windows/contrib/4.2/PACKAGES").
				AddBasicAuth(user.Name)
			resp := MakeRequest(t, req, http.StatusOK)

			assert.Contains(t, resp.Header().Get("Content-Type"), "text/plain")

			body := resp.Body.String()
			assert.Contains(t, body, fmt.Sprintf("Package: %s", packageName))
			assert.Contains(t, body, fmt.Sprintf("Version: %s", packageVersion))

			req = NewRequest(t, "GET", url+"/bin/windows/contrib/4.2/PACKAGES.gz").
				AddBasicAuth(user.Name)
			resp = MakeRequest(t, req, http.StatusOK)

			assert.Contains(t, resp.Header().Get("Content-Type"), "application/x-gzip")
		})
	})
}
