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

package actions

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

	"go.yaml.in/yaml/v4"
)

// ExtractJobPermissionsFromWorkflow extracts permissions from an already parsed workflow/job.
// It returns nil if neither workflow nor job explicitly specifies permissions.
func ExtractJobPermissionsFromWorkflow(flow *jobparser.SingleWorkflow, job *jobparser.Job) *repo_model.ActionsTokenPermissions {
	if flow == nil || job == nil {
		return nil
	}

	jobPerms := parseRawPermissionsExplicit(&job.RawPermissions)
	if jobPerms != nil {
		return jobPerms
	}

	workflowPerms := parseRawPermissionsExplicit(&flow.RawPermissions)
	if workflowPerms != nil {
		return workflowPerms
	}

	return nil
}

// parseRawPermissionsExplicit parses a YAML permissions node and returns only explicit scopes.
// It returns nil if the node does not explicitly specify permissions.
func parseRawPermissionsExplicit(rawPerms *yaml.Node) *repo_model.ActionsTokenPermissions {
	if rawPerms == nil || (rawPerms.Kind == yaml.ScalarNode && rawPerms.Value == "") {
		return nil
	}

	// Unwrap DocumentNode and resolve AliasNode
	node := rawPerms
	for node.Kind == yaml.DocumentNode || node.Kind == yaml.AliasNode {
		if node.Kind == yaml.DocumentNode {
			if len(node.Content) == 0 {
				return nil
			}
			node = node.Content[0]
		} else {
			node = node.Alias
		}
	}

	if node.Kind == yaml.ScalarNode && node.Value == "" {
		return nil
	}

	// Handle scalar values: "read-all" or "write-all"
	if node.Kind == yaml.ScalarNode {
		switch node.Value {
		case "read-all":
			return new(repo_model.MakeActionsTokenPermissions(perm.AccessModeRead))
		case "write-all":
			return new(repo_model.MakeActionsTokenPermissions(perm.AccessModeWrite))
		default:
			// Explicit but unrecognized scalar: return all-none permissions.
			return new(repo_model.MakeActionsTokenPermissions(perm.AccessModeNone))
		}
	}

	// Handle mapping: individual permission scopes
	if node.Kind == yaml.MappingNode {
		result := repo_model.MakeActionsTokenPermissions(perm.AccessModeNone)

		// Collect all scopes into a map first to handle priority
		scopes := make(map[string]perm.AccessMode)
		for i := 0; i < len(node.Content); i += 2 {
			if i+1 >= len(node.Content) {
				break
			}
			keyNode := node.Content[i]
			valueNode := node.Content[i+1]

			if keyNode.Kind != yaml.ScalarNode || valueNode.Kind != yaml.ScalarNode {
				continue
			}

			scopes[keyNode.Value] = parseAccessMode(valueNode.Value)
		}

		// 1. Apply 'contents' first (lower priority)
		if mode, ok := scopes["contents"]; ok {
			result.UnitAccessModes[unit.TypeCode] = mode
			result.UnitAccessModes[unit.TypeReleases] = mode
		}

		// 2. Apply all other scopes (overwrites contents if specified)
		for scope, mode := range scopes {
			switch scope {
			case "contents":
				// already handled
			case "code":
				result.UnitAccessModes[unit.TypeCode] = mode
			case "issues":
				result.UnitAccessModes[unit.TypeIssues] = mode
			case "pull-requests":
				result.UnitAccessModes[unit.TypePullRequests] = mode
			case "packages":
				result.UnitAccessModes[unit.TypePackages] = mode
			case "actions":
				result.UnitAccessModes[unit.TypeActions] = mode
			case "wiki":
				result.UnitAccessModes[unit.TypeWiki] = mode
			case "releases":
				result.UnitAccessModes[unit.TypeReleases] = mode
			case "projects":
				result.UnitAccessModes[unit.TypeProjects] = mode
			// Scopes github supports but gitea does not, see url for details
			// https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
			case "artifact-metadata", "attestations", "checks", "deployments",
				"id-token", "models", "discussions", "pages", "security-events", "statuses":
				// not supported
			default:
				setting.PanicInDevOrTesting("Unrecognized permission scope: %s", scope)
			}
		}

		return &result
	}

	return nil
}

// parseAccessMode converts a string access level to perm.AccessMode
func parseAccessMode(s string) perm.AccessMode {
	switch s {
	case "write":
		return perm.AccessModeWrite
	case "read":
		return perm.AccessModeRead
	default:
		return perm.AccessModeNone
	}
}
