// Copyright 2026 The Gitea Authors. All rights reserved.
// Copyright 2026 The nektos/act Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package runner

import (
	"bytes"
	"context"
	"strings"
	"testing"

	"gitea.com/gitea/act_runner/act/common"
	"gitea.com/gitea/act_runner/act/model"

	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	yaml "go.yaml.in/yaml/v4"
)

func TestRunScriptGroupTitle(t *testing.T) {
	sr := &stepRun{Step: &model.Step{Name: "Build"}}
	assert.Equal(t, "make build", sr.runScriptGroupTitle("make build"))
	assert.Equal(t, "echo one", sr.runScriptGroupTitle("  \techo one\necho two"))
	assert.Equal(t, "Build", sr.runScriptGroupTitle(""))

	sr = &stepRun{Step: &model.Step{ID: "s1"}}
	assert.Equal(t, "s1", sr.runScriptGroupTitle("\n  \n"))
}

func TestStepDeclaredEnvOrderPreservesYAML(t *testing.T) {
	raw := `id: s1
run: "echo 1"
env:
  GITHUB_TOKEN: tok
  PATH: /custom/bin
  MY_VAR: hello
`
	var step model.Step
	require.NoError(t, yaml.Unmarshal([]byte(raw), &step))
	assert.Equal(t, []string{"GITHUB_TOKEN", "PATH", "MY_VAR"}, stepDeclaredEnvKeysInOrder(&step))
}

func TestStepDeclaredEnvKeysInOrderEmpty(t *testing.T) {
	assert.Nil(t, stepDeclaredEnvKeysInOrder(nil))
	assert.Empty(t, stepDeclaredEnvKeysInOrder(&model.Step{}))
}

func TestStepDeclaredEnvKeysIgnoreYAMLMergeKey(t *testing.T) {
	doc := `
common: &common
  COMMON_A: a
  COMMON_B: b
step:
  env:
    LOCAL_BEFORE: before
    <<: *common
    COMMON_B: overridden
    LOCAL_AFTER: after
`
	var root struct {
		Step model.Step `yaml:"step"`
	}
	require.NoError(t, yaml.Unmarshal([]byte(doc), &root))

	keys := stepDeclaredEnvKeysInOrder(&root.Step)
	assert.Equal(t, []string{"LOCAL_BEFORE", "COMMON_B", "LOCAL_AFTER"}, keys)
}

func TestPrintRunScriptActionDetailsGolden(t *testing.T) {
	raw := `id: s1
name: Build
run: |
  echo one
  echo two
shell: pwsh
env:
  PATH_PREFIX: /custom/bin
  GITHUB_TOKEN: tok
  GREETING: hello
`
	var step model.Step
	require.NoError(t, yaml.Unmarshal([]byte(raw), &step))

	buf := &bytes.Buffer{}
	logger := logrus.New()
	logger.SetOutput(buf)
	logger.SetLevel(logrus.InfoLevel)
	logger.SetFormatter(&jobLogFormatter{color: cyan})
	entry := logger.WithFields(logrus.Fields{"job": "j1"})
	ctx := common.WithLogger(context.Background(), entry)

	sr := &stepRun{
		Step:               &step,
		RunContext:         &RunContext{},
		shellCommand:       "pwsh -command . '{0}'",
		interpolatedScript: "echo one\necho two\n",
		env: map[string]string{
			"PATH_PREFIX":  "/custom/bin",
			"GITHUB_TOKEN": "tok",
			"GREETING":     "hello",
		},
	}

	sr.printRunScriptActionDetails(ctx)

	want := strings.Join([]string{
		"[j1]   | ::group::Run echo one",
		"[j1]   | echo one",
		"[j1]   | echo two",
		"[j1]   | shell: pwsh -command . '{0}'",
		"[j1]   | env:",
		"[j1]   |   PATH_PREFIX: /custom/bin",
		"[j1]   |   GREETING: hello",
		"[j1]   | ::endgroup::",
		"",
	}, "\n")
	assert.Equal(t, want, buf.String())
}

func TestPrintRunActionHeaderGolden(t *testing.T) {
	raw := `id: s1
uses: actions/checkout@v4
with:
  fetch-depth: "0"
  token: secret
env:
  CUSTOM: value
  GITHUB_TOKEN: tok
`
	var step model.Step
	require.NoError(t, yaml.Unmarshal([]byte(raw), &step))

	buf := &bytes.Buffer{}
	logger := logrus.New()
	logger.SetOutput(buf)
	logger.SetLevel(logrus.InfoLevel)
	logger.SetFormatter(&jobLogFormatter{color: cyan})
	entry := logger.WithFields(logrus.Fields{"job": "j1"})
	ctx := common.WithLogger(context.Background(), entry)

	printRunActionHeader(ctx, &step, map[string]string{"CUSTOM": "value", "GITHUB_TOKEN": "tok"}, &RunContext{})

	want := strings.Join([]string{
		"[j1]   | ::group::Run actions/checkout@v4",
		"[j1]   | with:",
		"[j1]   |   fetch-depth: 0",
		"[j1]   |   token: secret",
		"[j1]   | env:",
		"[j1]   |   CUSTOM: value",
		"",
	}, "\n")
	assert.Equal(t, want, buf.String())
}

func TestIsInternalEnvKey(t *testing.T) {
	for _, k := range []string{"PATH", "HOME", "CI", "GITHUB_TOKEN", "GITEA_ACTIONS", "RUNNER_OS", "INPUT_FOO"} {
		assert.True(t, isInternalEnvKey(k, false), k)
	}
	for _, k := range []string{"PATH_PREFIX", "MY_VAR", "GREETING", "HOMEPAGE"} {
		assert.False(t, isInternalEnvKey(k, false), k)
	}
	assert.True(t, isInternalEnvKey("path", true))
	assert.False(t, isInternalEnvKey("path", false))
}

func TestPrintColoredScriptLineCyan(t *testing.T) {
	f := &jobLogFormatter{color: cyan}
	entry := &logrus.Entry{
		Level:   logrus.InfoLevel,
		Message: "echo one",
		Data: logrus.Fields{
			"job":               "j1",
			rawOutputField:      true,
			scriptLineCyanField: true,
		},
	}
	buf := &bytes.Buffer{}
	f.printColored(buf, entry)
	assert.Equal(t, "\x1b[36m|\x1b[0m \x1b[36;1mecho one\x1b[0m", buf.String())
}
