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

package actions

import (
	"testing"

	"gitea.dev/models/perm"
	repo_model "gitea.dev/models/repo"
	"gitea.dev/models/unit"
	"gitea.dev/modules/actions/jobparser"

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

func TestParseRawPermissions_ReadAll(t *testing.T) {
	var rawPerms yaml.Node
	err := yaml.Unmarshal([]byte(`read-all`), &rawPerms)
	assert.NoError(t, err)

	result := parseRawPermissionsExplicit(&rawPerms)
	require.NotNil(t, result)

	assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypeCode])
	assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypeIssues])
	assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypePullRequests])
	assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypePackages])
	assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypeActions])
	assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypeWiki])
	assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypeProjects])
}

// TestParseRawPermissions_GithubScopes verifies that all scopes that github supports are accounted for
func TestParseRawPermissions_GithubScopes(t *testing.T) {
	var rawPerms yaml.Node
	// Taken and stripped down from:
	// https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#defining-access-for-the-github_token-scopes
	yamlContent := `
actions: read
artifact-metadata: read
attestations: read
checks: read
contents: read
deployments: read
id-token: write
issues: read
models: read
discussions: read
packages: read
pages: read
pull-requests: read
security-events: read
statuses: read`
	err := yaml.Unmarshal([]byte(yamlContent), &rawPerms)
	require.NoError(t, err)

	result := parseRawPermissionsExplicit(&rawPerms)
	require.NotNil(t, result)

	// No asserts for permissions set on purpose
}

func TestParseRawPermissions_WriteAll(t *testing.T) {
	var rawPerms yaml.Node
	err := yaml.Unmarshal([]byte(`write-all`), &rawPerms)
	assert.NoError(t, err)

	result := parseRawPermissionsExplicit(&rawPerms)
	require.NotNil(t, result)

	assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeCode])
	assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeIssues])
	assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypePullRequests])
	assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypePackages])
	assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeActions])
	assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeWiki])
	assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeProjects])
}

func TestParseRawPermissions_IndividualScopes(t *testing.T) {
	yamlContent := `
contents: write
issues: read
pull-requests: none
packages: write
actions: read
wiki: write
projects: none
`
	var rawPerms yaml.Node
	err := yaml.Unmarshal([]byte(yamlContent), &rawPerms)
	assert.NoError(t, err)

	result := parseRawPermissionsExplicit(&rawPerms)
	require.NotNil(t, result)

	assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeCode])
	assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypeIssues])
	assert.Equal(t, perm.AccessModeNone, result.UnitAccessModes[unit.TypePullRequests])
	assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypePackages])
	assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypeActions])
	assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeWiki])
	assert.Equal(t, perm.AccessModeNone, result.UnitAccessModes[unit.TypeProjects])
}

func TestParseRawPermissions_Priority(t *testing.T) {
	t.Run("granular-wins-over-contents", func(t *testing.T) {
		yamlContent := `
contents: read
code: write
releases: none
`
		var rawPerms yaml.Node
		err := yaml.Unmarshal([]byte(yamlContent), &rawPerms)
		assert.NoError(t, err)

		result := parseRawPermissionsExplicit(&rawPerms)
		require.NotNil(t, result)

		assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeCode])
		assert.Equal(t, perm.AccessModeNone, result.UnitAccessModes[unit.TypeReleases])
	})

	t.Run("contents-applied-first", func(t *testing.T) {
		yamlContent := `
code: none
releases: write
contents: read
`
		var rawPerms yaml.Node
		err := yaml.Unmarshal([]byte(yamlContent), &rawPerms)
		assert.NoError(t, err)

		result := parseRawPermissionsExplicit(&rawPerms)
		require.NotNil(t, result)

		// code: none should win over contents: read
		assert.Equal(t, perm.AccessModeNone, result.UnitAccessModes[unit.TypeCode])
		// releases: write should win over contents: read
		assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeReleases])
	})
}

func TestParseRawPermissions_EmptyNode(t *testing.T) {
	var rawPerms yaml.Node
	// Empty node

	result := parseRawPermissionsExplicit(&rawPerms)

	// Should return nil for non-explicit
	assert.Nil(t, result)
}

func TestParseRawPermissions_NilNode(t *testing.T) {
	result := parseRawPermissionsExplicit(nil)

	// Should return nil
	assert.Nil(t, result)
}

func TestParseAccessMode(t *testing.T) {
	tests := []struct {
		input    string
		expected perm.AccessMode
	}{
		{"write", perm.AccessModeWrite},
		{"read", perm.AccessModeRead},
		{"none", perm.AccessModeNone},
		{"", perm.AccessModeNone},
		{"invalid", perm.AccessModeNone},
	}

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			result := parseAccessMode(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestExtractJobPermissionsFromWorkflow(t *testing.T) {
	workflowYAML := `
name: Test Permissions
on: workflow_dispatch
permissions: read-all

jobs:
  job-read-only:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Full read-only"

  job-none-perms:
    permissions: none
    runs-on: ubuntu-latest
    steps:
      - run: echo "Full read-only"

  job-override:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - run: echo "Override to write"
`

	expectedPerms := map[string]*repo_model.ActionsTokenPermissions{}
	expectedPerms["job-read-only"] = new(repo_model.MakeActionsTokenPermissions(perm.AccessModeRead))
	expectedPerms["job-none-perms"] = new(repo_model.MakeActionsTokenPermissions(perm.AccessModeNone))
	expectedPerms["job-override"] = new(repo_model.MakeActionsTokenPermissions(perm.AccessModeNone))
	expectedPerms["job-override"].UnitAccessModes[unit.TypeCode] = perm.AccessModeWrite
	expectedPerms["job-override"].UnitAccessModes[unit.TypeReleases] = perm.AccessModeWrite

	singleWorkflows, err := jobparser.Parse([]byte(workflowYAML))
	require.NoError(t, err)
	for _, flow := range singleWorkflows {
		jobID, jobDef := flow.Job()
		require.NotNil(t, jobDef)
		t.Run(jobID, func(t *testing.T) {
			assert.Equal(t, expectedPerms[jobID], ExtractJobPermissionsFromWorkflow(flow, jobDef))
		})
	}
}
