package login

import (
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

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

func TestRunLoginCmdExtractsTokenAndRedactsHelperOutput(t *testing.T) {
	cmd := exec.Command("sh", "-c", "printf 'JWT Token: test-token\\n'")

	token, err := runLoginCmd(cmd)

	require.NoError(t, err)
	assert.Equal(t, "test-token", token)
}

func TestRunLoginCmdSkipsEmptyTokenLine(t *testing.T) {
	cmd := exec.Command("sh", "-c", "printf 'JWT Token: \\nJWT Token: valid-token\\n'")

	token, err := runLoginCmd(cmd)

	require.NoError(t, err)
	assert.Equal(t, "valid-token", token)
}

func TestRunLoginCmdProcessErrorDoesNotEmbedHelperOutput(t *testing.T) {
	cmd := exec.Command("sh", "-c", "printf 'stdout-marker\\n'; printf 'stderr-marker\\n' >&2; exit 7")

	var token string
	var err error
	output := captureLoginOutput(t, func() {
		token, err = runLoginCmd(cmd)
	})

	assert.Empty(t, token)
	require.Error(t, err)
	assert.Equal(t, "failed to execute login helper: exit status 7", err.Error())
	assert.NotContains(t, err.Error(), "stdout-marker")
	assert.NotContains(t, err.Error(), "stderr-marker")
	assert.NotContains(t, err.Error(), "token")
	assert.Contains(t, output, "stdout-marker")
	assert.Contains(t, output, "stderr-marker")
}

func TestDockerCmdUsesOfficialPuppeteerImageAndReadOnlyScriptMount(t *testing.T) {
	cmd, err := dockerCmd("https://example.com", "user", "password")

	require.NoError(t, err)
	assertDockerLoginCmd(t, cmd)
}

func TestLoginCmdUsesDockerInLocalAndCI(t *testing.T) {
	local := false
	ci := true
	tests := map[string]struct {
		loginInCI       *bool
		messageContains string
	}{
		"local":     {loginInCI: &local, messageContains: "local machine"},
		"local nil": {loginInCI: nil, messageContains: "local machine"},
		"remote CI": {loginInCI: &ci, messageContains: "remote CI machine"},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			cmd, message, err := loginCmd(test.loginInCI, "https://example.com", "user", "password")

			require.NoError(t, err)
			assert.Contains(t, message, test.messageContains)
			assert.Nil(t, cmd.Env)
			assertDockerLoginCmd(t, cmd)
		})
	}
}

func assertDockerLoginCmd(t *testing.T, cmd *exec.Cmd) {
	t.Helper()

	assert.Equal(t, "docker", filepath.Base(cmd.Path))
	assert.Contains(t, cmd.Args, "--init")
	assert.Contains(t, cmd.Args, "--cap-add=SYS_ADMIN")
	assert.Contains(t, cmd.Args, "--rm")
	assert.Equal(t, "node", commandArgValue(t, cmd.Args, "--entrypoint"))
	assert.Contains(t, cmd.Args, "ghcr.io/puppeteer/puppeteer:16.1.0")
	assert.Contains(t, cmd.Args, "/home/pptruser/login.js")

	scriptMount := commandArgValue(t, cmd.Args, "--volume")
	assert.Contains(t, scriptMount, ":/home/pptruser/login.js:ro")
	assert.True(t, strings.HasPrefix(scriptMount, "/"))
}

func commandArgValue(t *testing.T, args []string, name string) string {
	t.Helper()

	for i, arg := range args {
		if arg == name {
			require.Less(t, i+1, len(args), "missing value for %s", name)
			return args[i+1]
		}
	}

	t.Fatalf("missing command argument %s", name)
	return ""
}

func TestRunLoginCmdRejectsEmptyToken(t *testing.T) {
	cmd := exec.Command("sh", "-c", "printf 'JWT Token: \\n'")

	token, err := runLoginCmd(cmd)

	assert.Empty(t, token)
	assert.EqualError(t, err, "login helper returned no JWT token")
}

func TestRedactTokenOutput(t *testing.T) {
	assert.Equal(t, "helper log\nJWT Token: [REDACTED]\n", redactTokenOutput("helper log\nJWT Token: test-token\n"))
}

func captureLoginOutput(t *testing.T, fn func()) string {
	t.Helper()

	originalStdout := os.Stdout
	reader, writer, err := os.Pipe()
	require.NoError(t, err)
	os.Stdout = writer

	fn()
	require.NoError(t, writer.Close())
	os.Stdout = originalStdout

	output, err := io.ReadAll(reader)
	require.NoError(t, err)
	require.NoError(t, reader.Close())
	return string(output)
}
