// Copyright (C) 2026 Yota Hamada
// SPDX-License-Identifier: GPL-3.0-or-later

package runtime

import (
	"context"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/dagucloud/dagu/internal/core"
	"github.com/dagucloud/dagu/internal/core/exec"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestExternalStepRetryEnabled(t *testing.T) {
	t.Run("DisabledByDefault", func(t *testing.T) {
		ctx := exec.NewContext(context.Background(), &core.DAG{Name: "test"}, "run-1", "test.log")
		assert.False(t, externalStepRetryEnabled(ctx))
	})

	t.Run("EnabledByProcessEnv", func(t *testing.T) {
		t.Setenv(exec.EnvKeyExternalStepRetry, "1")
		ctx := exec.NewContext(context.Background(), &core.DAG{Name: "test"}, "run-1", "test.log")
		assert.True(t, externalStepRetryEnabled(ctx))
	})

	t.Run("EnabledByExecutionContextEnv", func(t *testing.T) {
		_ = os.Unsetenv(exec.EnvKeyExternalStepRetry)
		ctx := exec.NewContext(
			context.Background(),
			&core.DAG{Name: "test"},
			"run-1",
			"test.log",
			exec.WithEnvVars(exec.EnvKeyExternalStepRetry+"=1"),
		)
		assert.True(t, externalStepRetryEnabled(ctx))
	})
}

func TestRunNodeExecution_ExternalStepRetrySkipsRepeatBookkeeping(t *testing.T) {
	t.Parallel()

	step := core.Step{
		Name: "retrying-step",
		Commands: []core.CommandEntry{
			{Command: "exit", Args: []string{"1"}, CmdWithArgs: "exit 1"},
		},
		RetryPolicy: core.RetryPolicy{
			Limit:    1,
			Interval: 5 * time.Second,
		},
		RepeatPolicy: core.RepeatPolicy{
			RepeatMode: core.RepeatModeWhile,
			Interval:   time.Millisecond,
		},
	}
	plan, err := NewPlan(step)
	require.NoError(t, err)

	node := plan.GetNodeByName(step.Name)
	require.NotNil(t, node)

	logDir := t.TempDir()
	runner := New(&Config{
		DAGRunID: "run-1",
		LogDir:   logDir,
	})
	ctx := NewContext(
		context.Background(),
		&core.DAG{Name: "retry-dag", WorkingDir: logDir},
		"run-1",
		filepath.Join(logDir, "dag.log"),
		exec.WithEnvVars(exec.EnvKeyExternalStepRetry+"=1"),
	)
	require.NoError(t, node.Prepare(ctx, logDir, "run-1"))

	runner.runNodeExecution(ctx, plan, node, nil)
	require.NoError(t, node.Teardown())

	assert.Equal(t, core.NodeRetrying, node.State().Status)
	assert.Equal(t, 0, node.State().DoneCount)
	assert.Equal(t, 1, node.State().RetryCount)
}

func TestSetupVariables_StepEnvEvaluatesSequentiallyWithRuntimeVars(t *testing.T) {
	t.Parallel()

	envs := []string{
		"WORK_DIR=${DAG_RUN_ARTIFACTS_DIR}",
		"CURRENT_IDEA_PATH=${WORK_DIR}/current_idea.md",
	}
	tests := []struct {
		name         string
		step         core.Step
		dagContainer *core.Container
	}{
		{
			name: "step env",
			step: core.Step{
				Name: "render",
				Env:  envs,
			},
		},
		{
			name: "step container env",
			step: core.Step{
				Name:      "render",
				Container: &core.Container{Env: envs},
			},
		},
		{
			name: "dag container fallback env",
			step: core.Step{Name: "render"},
			dagContainer: &core.Container{
				Env: envs,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			artifactDir := filepath.Join(t.TempDir(), "artifacts", "run-1")
			plan, err := NewPlan(tt.step)
			require.NoError(t, err)
			node := plan.GetNodeByName(tt.step.Name)
			require.NotNil(t, node)

			runner := New(&Config{})
			ctx := NewContext(
				context.Background(),
				&core.DAG{
					Name:       "test-dag",
					WorkingDir: t.TempDir(),
					Container:  tt.dagContainer,
				},
				"run-1",
				filepath.Join(t.TempDir(), "dag.log"),
				WithArtifactDir(artifactDir),
			)

			ctx = runner.setupVariables(ctx, plan, node)

			result := AllEnvsMap(ctx)
			assert.Equal(t, artifactDir, result["WORK_DIR"])
			assert.Equal(t, filepath.Join(artifactDir, "current_idea.md"), filepath.Clean(result["CURRENT_IDEA_PATH"]))
		})
	}
}
