package security

import "github.com/enmanuel/agents/pkg/acl"

// ResolveACL computes the ACL for a given agentID from a SecurityPolicy.
//
// Resolution rules:
//   - An AgentPolicy applies to agentID if its AgentGroup field is:
//     (a) a group name in p.AgentGroups whose Agents list contains agentID or "*", or
//     (b) directly equal to agentID (individual assignment without a named group).
//   - If multiple AgentPolicies apply, their permissions are accumulated (union).
//   - For each Permission, the UserGroup is resolved to members in p.UserGroups.
//     A UserGroup with Members = ["*"] grants the actions to all users.
//   - If no policy applies, an empty ACL is returned (open access per acl semantics).
func ResolveACL(agentID string, p SecurityPolicy) acl.ACL {
	// Build a lookup: group name → members.
	userGroupMembers := make(map[string][]string, len(p.UserGroups))
	for _, ug := range p.UserGroups {
		userGroupMembers[ug.Name] = ug.Members
	}

	// Collect all roles from every AgentPolicy that applies to this agent.
	var roles []acl.Role
	for _, ap := range p.Policies {
		if !agentPolicyApplies(agentID, ap.AgentGroup, p.AgentGroups) {
			continue
		}
		for _, perm := range ap.Permissions {
			members := resolveMembers(perm.UserGroup, userGroupMembers)
			roles = append(roles, acl.Role{
				Name:    perm.UserGroup,
				Users:   members,
				Actions: perm.Actions,
			})
		}
	}

	return acl.FromRoles(roles)
}

// agentPolicyApplies returns true if an AgentPolicy with the given agentGroupRef
// should apply to agentID.
func agentPolicyApplies(agentID, agentGroupRef string, groups []AgentGroup) bool {
	// Try to find a named group first.
	for _, ag := range groups {
		if ag.Name != agentGroupRef {
			continue
		}
		for _, a := range ag.Agents {
			if a == "*" || a == agentID {
				return true
			}
		}
		return false // group found but agent not in it
	}
	// No matching group found — treat agentGroupRef as a direct agent ID.
	return agentGroupRef == agentID
}

// resolveMembers returns the member list for a user group name.
// If the group is not defined, it falls back to treating the name as a literal user ID.
func resolveMembers(userGroupName string, lookup map[string][]string) []string {
	if members, ok := lookup[userGroupName]; ok {
		return members
	}
	// Fallback: treat as a direct user ID.
	return []string{userGroupName}
}
