package env

import (
	"fmt"
	"os"
	"syscall"
	"testing"

	"github.com/goreleaser/goreleaser/v2/internal/skips"
	"github.com/goreleaser/goreleaser/v2/internal/testctx"
	"github.com/goreleaser/goreleaser/v2/internal/testlib"
	"github.com/goreleaser/goreleaser/v2/pkg/config"
	"github.com/goreleaser/goreleaser/v2/pkg/context"
	"github.com/stretchr/testify/require"
)

func TestMain(m *testing.M) {
	restores := map[string]string{}
	for _, key := range []string{"GITHUB_TOKEN", "GITEA_TOKEN", "GITLAB_TOKEN"} {
		prevValue, ok := os.LookupEnv(key)
		if ok {
			_ = os.Unsetenv(key)
			restores[key] = prevValue
		}
	}

	m.Run()

	for k, v := range restores {
		_ = os.Setenv(k, v)
	}
}

func TestDescription(t *testing.T) {
	require.NotEmpty(t, Pipe{}.String())
}

func TestSetDefaultTokenFiles(t *testing.T) {
	t.Run("empty config", func(t *testing.T) {
		ctx := testctx.New()
		setDefaultTokenFiles(ctx)
		require.Equal(t, "~/.config/goreleaser/github_token", ctx.Config.EnvFiles.GitHubToken)
		require.Equal(t, "~/.config/goreleaser/gitlab_token", ctx.Config.EnvFiles.GitLabToken)
		require.Equal(t, "~/.config/goreleaser/gitea_token", ctx.Config.EnvFiles.GiteaToken)
	})
	t.Run("custom config config", func(t *testing.T) {
		cfg := "what"
		ctx := testctx.NewWithCfg(config.Project{
			EnvFiles: config.EnvFiles{
				GitHubToken: cfg,
			},
		})
		setDefaultTokenFiles(ctx)
		require.Equal(t, cfg, ctx.Config.EnvFiles.GitHubToken)
	})
	t.Run("templates", func(t *testing.T) {
		ctx := testctx.NewWithCfg(config.Project{
			ProjectName: "foobar",
			Env: []string{
				"FOO=FOO_{{ .Env.BAR }}",
				"FOOBAR={{.ProjectName}}",
				"EMPTY_VAL=",
			},
		})
		ctx.Env["FOOBAR"] = "old foobar"
		t.Setenv("BAR", "lebar")
		t.Setenv("GITHUB_TOKEN", "fake")
		require.NoError(t, Pipe{}.Run(ctx))
		require.Equal(t, "FOO_lebar", ctx.Env["FOO"])
		require.Equal(t, "foobar", ctx.Env["FOOBAR"])
		require.Equal(t, "", ctx.Env["EMPTY_VAL"])
	})

	t.Run("template error", func(t *testing.T) {
		ctx := testctx.NewWithCfg(config.Project{
			Env: []string{
				"FOO={{ .Asss }",
			},
		})
		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
	})

	t.Run("no token", func(t *testing.T) {
		ctx := testctx.New()
		require.EqualError(t, Pipe{}.Run(ctx), ErrMissingToken.Error())
	})
}

func TestForceToken(t *testing.T) {
	t.Run("github", func(t *testing.T) {
		t.Setenv("GITHUB_TOKEN", "fake")
		t.Setenv("GORELEASER_FORCE_TOKEN", "github")
		ctx := testctx.New()
		require.NoError(t, Pipe{}.Run(ctx))
		require.Equal(t, context.TokenTypeGitHub, ctx.TokenType)
	})
	t.Run("gitlab", func(t *testing.T) {
		t.Setenv("GITLAB_TOKEN", "fake")
		t.Setenv("GORELEASER_FORCE_TOKEN", "gitlab")
		ctx := testctx.New()
		require.NoError(t, Pipe{}.Run(ctx))
		require.Equal(t, context.TokenTypeGitLab, ctx.TokenType)
	})
	t.Run("gitea", func(t *testing.T) {
		t.Setenv("GITEA_TOKEN", "fake")
		t.Setenv("GORELEASER_FORCE_TOKEN", "gitea")
		ctx := testctx.New()
		require.NoError(t, Pipe{}.Run(ctx))
		require.Equal(t, context.TokenTypeGitea, ctx.TokenType)
	})
}

func TestValidGithubEnv(t *testing.T) {
	t.Setenv("GITHUB_TOKEN", "asdf")
	ctx := testctx.New()
	require.NoError(t, Pipe{}.Run(ctx))
	require.Equal(t, "asdf", ctx.Token)
	require.Equal(t, context.TokenTypeGitHub, ctx.TokenType)
}

func TestValidGitlabEnv(t *testing.T) {
	t.Setenv("GITLAB_TOKEN", "qwertz")
	ctx := testctx.New()
	require.NoError(t, Pipe{}.Run(ctx))
	require.Equal(t, "qwertz", ctx.Token)
	require.Equal(t, context.TokenTypeGitLab, ctx.TokenType)
}

func TestValidGiteaEnv(t *testing.T) {
	t.Setenv("GITEA_TOKEN", "token")
	ctx := testctx.New()
	require.NoError(t, Pipe{}.Run(ctx))
	require.Equal(t, "token", ctx.Token)
	require.Equal(t, context.TokenTypeGitea, ctx.TokenType)
}

func TestInvalidEnv(t *testing.T) {
	ctx := testctx.New()
	require.Error(t, Pipe{}.Run(ctx))
	require.EqualError(t, Pipe{}.Run(ctx), ErrMissingToken.Error())
}

func TestMultipleEnvTokens(t *testing.T) {
	t.Setenv("GITHUB_TOKEN", "asdf")
	t.Setenv("GITLAB_TOKEN", "qwertz")
	t.Setenv("GITEA_TOKEN", "token")
	ctx := testctx.New()
	require.Error(t, Pipe{}.Run(ctx))
	require.EqualError(t, Pipe{}.Run(ctx), "multiple tokens found, but only one is allowed: GITHUB_TOKEN, GITLAB_TOKEN, GITEA_TOKEN\n\nLearn more at https://goreleaser.com/errors/multiple-tokens\n")
}

func TestMultipleEnvTokensForce(t *testing.T) {
	t.Setenv("GITHUB_TOKEN", "asdf")
	t.Setenv("GITLAB_TOKEN", "qwertz")
	t.Setenv("GITEA_TOKEN", "token")
	ctx := testctx.New()
	require.Error(t, Pipe{}.Run(ctx))
	require.EqualError(t, Pipe{}.Run(ctx), "multiple tokens found, but only one is allowed: GITHUB_TOKEN, GITLAB_TOKEN, GITEA_TOKEN\n\nLearn more at https://goreleaser.com/errors/multiple-tokens\n")
}

func TestEmptyGithubFileEnv(t *testing.T) {
	ctx := testctx.New()
	require.Error(t, Pipe{}.Run(ctx))
}

func TestEmptyGitlabFileEnv(t *testing.T) {
	ctx := testctx.New()
	require.Error(t, Pipe{}.Run(ctx))
}

func TestEmptyGiteaFileEnv(t *testing.T) {
	ctx := testctx.New()
	require.Error(t, Pipe{}.Run(ctx))
}

func TestEmptyGithubEnvFile(t *testing.T) {
	f, err := os.CreateTemp(t.TempDir(), "token")
	require.NoError(t, err)
	require.NoError(t, f.Close())
	require.NoError(t, os.Chmod(f.Name(), 0o377))
	ctx := testctx.NewWithCfg(config.Project{
		EnvFiles: config.EnvFiles{
			GitHubToken: f.Name(),
		},
	})
	err = Pipe{}.Run(ctx)
	requireErrAccess(t, err)
	require.ErrorContains(t, err, "failed to load github token")
}

func TestEmptyGitlabEnvFile(t *testing.T) {
	f, err := os.CreateTemp(t.TempDir(), "token")
	require.NoError(t, err)
	require.NoError(t, f.Close())
	require.NoError(t, os.Chmod(f.Name(), 0o377))
	ctx := testctx.NewWithCfg(config.Project{
		EnvFiles: config.EnvFiles{
			GitLabToken: f.Name(),
		},
	})
	err = Pipe{}.Run(ctx)
	requireErrAccess(t, err)
	require.ErrorContains(t, err, "failed to load gitlab token")
}

func TestEmptyGiteaEnvFile(t *testing.T) {
	f, err := os.CreateTemp(t.TempDir(), "token")
	require.NoError(t, err)
	require.NoError(t, f.Close())
	require.NoError(t, os.Chmod(f.Name(), 0o377))
	ctx := testctx.NewWithCfg(config.Project{
		EnvFiles: config.EnvFiles{
			GiteaToken: f.Name(),
		},
	})
	err = Pipe{}.Run(ctx)
	requireErrAccess(t, err)
	require.ErrorContains(t, err, "failed to load gitea token")
}

func TestInvalidEnvChecksSkipped(t *testing.T) {
	ctx := testctx.New(testctx.Skip(skips.Publish))
	require.NoError(t, Pipe{}.Run(ctx))
}

func TestInvalidEnvReleaseDisabled(t *testing.T) {
	t.Run("true", func(t *testing.T) {
		ctx := testctx.NewWithCfg(config.Project{
			Env: []string{},
			Release: config.Release{
				Disable: "true",
			},
		})
		require.NoError(t, Pipe{}.Run(ctx))
	})

	t.Run("tmpl true", func(t *testing.T) {
		ctx := testctx.NewWithCfg(config.Project{
			Env: []string{"FOO=true"},
			Release: config.Release{
				Disable: "{{ .Env.FOO }}",
			},
		})
		require.NoError(t, Pipe{}.Run(ctx))
	})

	t.Run("tmpl false", func(t *testing.T) {
		ctx := testctx.NewWithCfg(config.Project{
			Env: []string{"FOO=true"},
			Release: config.Release{
				Disable: "{{ .Env.FOO }}-nope",
			},
		})
		require.EqualError(t, Pipe{}.Run(ctx), ErrMissingToken.Error())
	})

	t.Run("tmpl error", func(t *testing.T) {
		ctx := testctx.NewWithCfg(config.Project{
			Release: config.Release{
				Disable: "{{ .Env.FOO }}",
			},
		})
		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
	})
}

func TestLoadEnv(t *testing.T) {
	const env = "SUPER_SECRET_ENV_NOPE"

	t.Run("env exists", func(t *testing.T) {
		t.Setenv(env, "1")
		v, err := loadEnv(env, "nope")
		require.NoError(t, err)
		require.Equal(t, "1", v)
	})
	t.Run("env file exists", func(t *testing.T) {
		f, err := os.CreateTemp(t.TempDir(), "token")
		require.NoError(t, err)
		fmt.Fprintf(f, "123")
		require.NoError(t, f.Close())
		v, err := loadEnv(env, f.Name())
		require.NoError(t, err)
		require.Equal(t, "123", v)
	})
	t.Run("env file with an empty line at the end", func(t *testing.T) {
		f, err := os.CreateTemp(t.TempDir(), "token")
		require.NoError(t, err)
		fmt.Fprintf(f, "123\n")
		require.NoError(t, f.Close())
		v, err := loadEnv(env, f.Name())
		require.NoError(t, err)
		require.Equal(t, "123", v)
	})
	t.Run("env file is not readable", func(t *testing.T) {
		testlib.SkipIfWindows(t, "permissions work differently in windows")
		f, err := os.CreateTemp(t.TempDir(), "token")
		require.NoError(t, err)
		fmt.Fprintf(f, "123")
		require.NoError(t, f.Close())
		err = os.Chmod(f.Name(), 0o377)
		require.NoError(t, err)
		v, err := loadEnv(env, f.Name())
		require.EqualError(t, err, fmt.Sprintf("open %s: permission denied", f.Name()))
		require.Equal(t, "", v)
	})
}

func requireErrAccess(tb testing.TB, err error) {
	tb.Helper()
	require.Error(tb, err)
	// unsupported
	if testlib.IsWindows() {
		return
	}
	require.ErrorIs(tb, err, syscall.EACCES)
}