// Package utils provides utility functions for AWS IAM ARN parsing and role extraction.
package utils

import "strings"

// ARN parsing constants for AWS IAM and STS services
const (
	// stsPrefix is the common prefix for all AWS STS ARNs
	stsPrefix = "arn:aws:sts::"

	// stsAssumedRoleMarker is the marker that identifies assumed role ARNs
	stsAssumedRoleMarker = "assumed-role/"

	// iamPrefix is the common prefix for all AWS IAM ARNs
	iamPrefix = "arn:aws:iam::"

	// iamRoleMarker is the marker that identifies IAM role ARNs
	iamRoleMarker = "role/"

	// iamUserMarker is the marker that identifies IAM user ARNs
	iamUserMarker = "user/"
)

// ARNInfo contains structured information about a parsed AWS ARN.
// This provides more context than a simple string extraction, making it
// easier to debug issues and support different ARN formats.
type ARNInfo struct {
	// Original is the original ARN string that was parsed
	Original string

	// RoleName is the extracted role name (without "role/" prefix)
	// May include path components (e.g., "Division/Team/RoleName")
	// Empty string indicates an invalid ARN
	RoleName string

	// AccountID is the AWS account ID if present in the ARN
	// Empty string for legacy format ARNs (arn:aws:iam::role/Name)
	// Non-empty for standard format ARNs (arn:aws:iam::ACCOUNT:role/Name)
	AccountID string
}

// ExtractRoleNameFromPrincipal extracts the role name from an AWS principal ARN.
//
// It handles both STS assumed role and IAM role ARN formats, supporting both
// legacy (without account ID) and standard AWS (with account ID) formats:
//   - arn:aws:sts::assumed-role/RoleName/SessionName (legacy STS format)
//   - arn:aws:sts::ACCOUNT:assumed-role/RoleName/SessionName (standard STS format)
//   - arn:aws:iam::role/RoleName (legacy IAM format)
//   - arn:aws:iam::ACCOUNT:role/RoleName (standard IAM format)
//
// For STS assumed-role ARNs, it extracts the role name from the "assumed-role/"
// component, which is the second part of the path (e.g., "Role" from
// "arn:aws:sts::ACCOUNT:assumed-role/Role/Session"). For IAM role ARNs, it
// delegates to ExtractRoleNameFromArn.
//
// Returns an empty string if the principal format is invalid or unrecognized.
//
// Parameters:
//   - principal: The AWS principal ARN string to extract the role name from
//
// Returns:
//   - The extracted role name (without "role/" prefix)
//   - Empty string if the principal is invalid or no role name is found
func ExtractRoleNameFromPrincipal(principal string) string {
	// Handle STS assumed role format
	if strings.HasPrefix(principal, stsPrefix) {
		remainder := principal[len(stsPrefix):]

		// Validate ARN structure: should be either "assumed-role/..." or "ACCOUNT:assumed-role/..."
		// Split on ':' to separate account ID (if present) from resource type
		resourcePart := remainder
		if colonIdx := strings.Index(remainder, ":"); colonIdx != -1 {
			// Standard format with account ID: "ACCOUNT:assumed-role/..."
			resourcePart = remainder[colonIdx+1:]
		}

		// Verify the resource type is exactly "assumed-role/"
		if !strings.HasPrefix(resourcePart, stsAssumedRoleMarker) {
			return ""
		}

		// Extract role name after "assumed-role/"
		afterMarker := resourcePart[len(stsAssumedRoleMarker):]
		if slash := strings.Index(afterMarker, "/"); slash != -1 {
			return afterMarker[:slash]
		}
		return afterMarker
	}

	// Handle IAM role format
	return ExtractRoleNameFromArn(principal)
}

// ExtractUserNameFromPrincipal extracts the user name from an AWS IAM principal ARN.
//
// It handles both legacy and standard AWS IAM user ARN formats:
//   - arn:aws:iam::user/UserName (legacy format without account ID)
//   - arn:aws:iam::ACCOUNT:user/UserName (standard AWS format with account ID)
//
// Returns an empty string if the principal does not represent an IAM user.
func ExtractUserNameFromPrincipal(principal string) string {
	if !strings.HasPrefix(principal, iamPrefix) {
		return ""
	}

	remainder := principal[len(iamPrefix):]
	resourcePart := remainder
	if colonIdx := strings.Index(remainder, ":"); colonIdx != -1 {
		resourcePart = remainder[colonIdx+1:]
	}

	if !strings.HasPrefix(resourcePart, iamUserMarker) {
		return ""
	}

	userName := resourcePart[len(iamUserMarker):]
	if userName == "" {
		return ""
	}

	return userName
}

// ExtractRoleNameFromArn extracts the role name from an AWS IAM role ARN.
//
// It handles both legacy and standard AWS IAM role ARN formats:
//   - arn:aws:iam::role/RoleName (legacy format without account ID)
//   - arn:aws:iam::ACCOUNT:role/RoleName (standard AWS format with account ID)
//
// The function validates the ARN structure to ensure the resource type is exactly
// "role", preventing security issues where malicious ARNs like
// "arn:aws:iam::123456789012:user/role/malicious" could be accepted.
//
// If the ARN contains a path component (e.g., "role/Division/Team/RoleName"),
// the entire path is returned after "role/".
//
// Returns an empty string if:
//   - The ARN does not start with "arn:aws:iam::"
//   - The resource type is not exactly "role"
//   - The input is empty or invalid
//
// This function is commonly used in STS (Security Token Service) role assumption
// validation to extract the role name from principal ARNs or to validate IAM
// role ARNs during credential assumption checks.
//
// Parameters:
//   - roleArn: The IAM role ARN string to extract the role name from
//
// Returns:
//   - The extracted role name (without "role/" prefix, may include path)
//   - Empty string if the ARN is invalid or no role name is found
func ExtractRoleNameFromArn(roleArn string) string {
	if !strings.HasPrefix(roleArn, iamPrefix) {
		return ""
	}

	remainder := roleArn[len(iamPrefix):]

	// Validate ARN structure: should be either "role/..." or "ACCOUNT:role/..."
	// Split on ':' to separate account ID (if present) from resource type
	resourcePart := remainder
	if colonIdx := strings.Index(remainder, ":"); colonIdx != -1 {
		// Standard format with account ID: "ACCOUNT:role/..."
		resourcePart = remainder[colonIdx+1:]
	}

	// Verify the resource type is exactly "role/"
	if !strings.HasPrefix(resourcePart, iamRoleMarker) {
		return ""
	}

	// Extract role name after "role/"
	return resourcePart[len(iamRoleMarker):]
}

// ParseRoleARN parses an AWS IAM role ARN and returns structured information.
//
// It handles both legacy and standard AWS IAM role ARN formats:
//   - arn:aws:iam::role/RoleName (legacy format without account ID)
//   - arn:aws:iam::ACCOUNT:role/RoleName (standard AWS format with account ID)
//
// The function validates the ARN structure to ensure the resource type is exactly
// "role", extracting the role name and account ID (if present).
//
// Parameters:
//   - roleArn: The IAM role ARN string to parse
//
// Returns:
//   - ARNInfo struct containing parsed information
//   - RoleName will be empty if parsing fails or ARN is malformed
func ParseRoleARN(roleArn string) ARNInfo {
	info := ARNInfo{
		Original: roleArn,
	}

	if !strings.HasPrefix(roleArn, iamPrefix) {
		return info
	}

	remainder := roleArn[len(iamPrefix):]

	// Validate ARN structure: should be either "role/..." or "ACCOUNT:role/..."
	// Split on ':' to separate account ID (if present) from resource type
	resourcePart := remainder
	accountPart := ""

	if colonIdx := strings.Index(remainder, ":"); colonIdx != -1 {
		// Standard format with account ID: "ACCOUNT:role/..."
		accountPart = remainder[:colonIdx]
		resourcePart = remainder[colonIdx+1:]
	}

	// Verify the resource type is exactly "role/"
	if !strings.HasPrefix(resourcePart, iamRoleMarker) {
		// Invalid resource type
		return info
	}

	// Extract role name (everything after "role/")
	info.RoleName = resourcePart[len(iamRoleMarker):]
	if info.RoleName == "" {
		// Empty role name is invalid
		return info
	}

	// Set account ID if present
	info.AccountID = accountPart

	return info
}

// ParsePrincipalARN parses an AWS principal ARN (STS or IAM) and returns structured information.
//
// It handles both STS assumed role and IAM role ARN formats:
//   - arn:aws:sts::assumed-role/RoleName/SessionName (legacy STS format)
//   - arn:aws:sts::ACCOUNT:assumed-role/RoleName/SessionName (standard STS format)
//   - arn:aws:iam::role/RoleName (legacy IAM format)
//   - arn:aws:iam::ACCOUNT:role/RoleName (standard IAM format)
//
// The function validates the ARN structure to ensure the resource type is exactly
// "assumed-role" for STS or delegates to ParseRoleARN for IAM ARNs.
//
// Parameters:
//   - principal: The AWS principal ARN string to parse
//
// Returns:
//   - ARNInfo struct containing parsed information
//   - RoleName will be empty if parsing fails or ARN is malformed
func ParsePrincipalARN(principal string) ARNInfo {
	// Handle STS assumed role format
	if strings.HasPrefix(principal, stsPrefix) {
		info := ARNInfo{
			Original: principal,
		}

		remainder := principal[len(stsPrefix):]

		// Validate ARN structure: should be either "assumed-role/..." or "ACCOUNT:assumed-role/..."
		// Split on ':' to separate account ID (if present) from resource type
		resourcePart := remainder
		accountPart := ""

		if colonIdx := strings.Index(remainder, ":"); colonIdx != -1 {
			// Standard format with account ID: "ACCOUNT:assumed-role/..."
			accountPart = remainder[:colonIdx]
			resourcePart = remainder[colonIdx+1:]
		}

		// Verify the resource type is exactly "assumed-role/"
		if !strings.HasPrefix(resourcePart, stsAssumedRoleMarker) {
			// Invalid resource type
			return info
		}

		// Extract role name (between "assumed-role/" and next "/")
		afterMarker := resourcePart[len(stsAssumedRoleMarker):]
		if slash := strings.Index(afterMarker, "/"); slash != -1 {
			info.RoleName = afterMarker[:slash]
		} else {
			info.RoleName = afterMarker
		}

		// Validate that role name is not empty
		if info.RoleName == "" {
			return info
		}

		// Set account ID if present
		info.AccountID = accountPart

		return info
	}

	// Handle IAM role format
	return ParseRoleARN(principal)
}
