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

package actions

import (
	"strconv"
	"testing"

	actions_model "code.gitea.io/gitea/models/actions"
	"code.gitea.io/gitea/models/unittest"

	act_model "github.com/nektos/act/pkg/model"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestEvaluateRunConcurrency_RunIDFallback(t *testing.T) {
	// Unit-level check that EvaluateRunConcurrencyFillModel resolves
	// github.run_id from run.ID. The full-flow regression — that run.ID is
	// non-zero by the time evaluation happens — is in
	// TestPrepareRunAndInsert_ExpressionsSeeRunID.
	assert.NoError(t, unittest.PrepareTestDatabase())
	ctx := t.Context()

	runA := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 791})
	runB := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 792})

	expr := &act_model.RawConcurrency{
		Group:            "${{ github.workflow }}-${{ github.head_ref || github.run_id }}",
		CancelInProgress: "true",
	}

	assert.NoError(t, EvaluateRunConcurrencyFillModel(ctx, runA, expr, nil, nil))
	assert.NoError(t, EvaluateRunConcurrencyFillModel(ctx, runB, expr, nil, nil))

	assert.Contains(t, runA.ConcurrencyGroup, "791")
	assert.Contains(t, runB.ConcurrencyGroup, "792")
	assert.NotEqual(t, runA.ConcurrencyGroup, runB.ConcurrencyGroup)
}

func TestPrepareRunAndInsert_ExpressionsSeeRunID(t *testing.T) {
	// Regression for the cross-branch concurrency leak: github.run_id must
	// be available during BOTH jobparser.Parse (run-name) and workflow-level
	// concurrency evaluation. Re-ordering db.Insert relative to either step
	// would leave run.ID at 0 and break this test.
	assert.NoError(t, unittest.PrepareTestDatabase())
	ctx := t.Context()

	content := []byte(`name: cross-branch
run-name: "Run ${{ github.run_id }}"
on: push
concurrency:
  group: group-${{ github.run_id }}
  cancel-in-progress: true
jobs:
  hello:
    runs-on: ubuntu-latest
    steps:
      - run: echo hi
`)

	run := &actions_model.ActionRun{
		Title:         "before parse",
		RepoID:        4,
		OwnerID:       1,
		WorkflowID:    "expr-runid.yaml",
		TriggerUserID: 1,
		Ref:           "refs/heads/master",
		CommitSHA:     "c2d72f548424103f01ee1dc02889c1e2bff816b0",
		Event:         "push",
		TriggerEvent:  "push",
		EventPayload:  "{}",
	}
	require.NoError(t, PrepareRunAndInsert(ctx, content, run, nil))
	require.Positive(t, run.ID)

	persisted := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run.ID})
	runIDStr := strconv.FormatInt(run.ID, 10)
	assert.Equal(t, "Run "+runIDStr, persisted.Title)
	assert.Equal(t, "group-"+runIDStr, persisted.ConcurrencyGroup)
	// Rerun reads raw_concurrency from the DB to re-evaluate the group;
	// see services/actions/rerun.go. Must survive the insert.
	assert.NotEmpty(t, persisted.RawConcurrency)
}

func TestFindTaskNeeds(t *testing.T) {
	assert.NoError(t, unittest.PrepareTestDatabase())

	task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 51})
	job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: task.JobID})

	ret, err := FindTaskNeeds(t.Context(), job)
	assert.NoError(t, err)
	assert.Len(t, ret, 1)
	assert.Contains(t, ret, "job1")
	assert.Len(t, ret["job1"].Outputs, 2)
	assert.Equal(t, "abc", ret["job1"].Outputs["output_a"])
	assert.Equal(t, "bbb", ret["job1"].Outputs["output_b"])
}
