package analyzers

import (
	"go/ast"
	"go/token"
	"strings"

	"golang.org/x/tools/go/analysis"
)

// NoInlineCredentialsAnalyzer flags struct literals of type worker.Config where
// sensitive fields (RedisPassword, SecretKey, AccessKey) are set to non-empty string
// literals instead of environment variable references. Credentials must not appear
// in source or config files.
var NoInlineCredentialsAnalyzer = &analysis.Analyzer{
	Name: "noinlinecreds",
	Doc:  "flags inline credentials in Config struct literals",
	Run:  runNoInlineCredentials,
}

// sensitiveCredentialFields lists field names that should never have inline string literals
var sensitiveCredentialFields = []string{
	"RedisPassword",
	"SecretKey",
	"AccessKey",
	"Password",
	"APIKey",
	"Token",
	"PrivateKey",
}

func runNoInlineCredentials(pass *analysis.Pass) (interface{}, error) {
	for _, file := range pass.Files {
		ast.Inspect(file, func(n ast.Node) bool {
			// Look for composite literals (struct initialization)
			composite, ok := n.(*ast.CompositeLit)
			if !ok {
				return true
			}

			// Check if this is a Config type
			typeInfo := pass.TypesInfo.TypeOf(composite)
			if typeInfo == nil {
				return true
			}

			typeStr := typeInfo.String()
			// Match worker.Config or Config types
			if !strings.Contains(typeStr, "Config") {
				return true
			}

			// Check each field in the composite literal
			for _, elt := range composite.Elts {
				kv, ok := elt.(*ast.KeyValueExpr)
				if !ok {
					continue
				}

				key, ok := kv.Key.(*ast.Ident)
				if !ok {
					continue
				}

				// Check if this is a sensitive field
				if !isSensitiveField(key.Name) {
					continue
				}

				// Check if the value is a string literal (not env var or function call)
				if isInlineStringLiteral(kv.Value) {
					// Check if it's not empty (empty strings might be intentional)
					if !isEmptyStringLiteral(kv.Value) {
						pass.Reportf(kv.Value.Pos(),
							"inline credential detected in field %s - use environment variables instead (e.g., os.Getenv)",
							key.Name)
					}
				}
			}

			return true
		})
	}
	return nil, nil
}

// isSensitiveField checks if a field name is in the sensitive credentials list
func isSensitiveField(name string) bool {
	for _, sensitive := range sensitiveCredentialFields {
		if name == sensitive {
			return true
		}
	}
	return false
}

// isInlineStringLiteral checks if an expression is a string literal (not env var ref)
func isInlineStringLiteral(expr ast.Expr) bool {
	switch e := expr.(type) {
	case *ast.BasicLit:
		// String literal like "password123"
		return e.Kind == token.STRING
	case *ast.BinaryExpr:
		// String concatenation like "prefix" + "suffix"
		if e.Op == token.ADD {
			return isInlineStringLiteral(e.X) || isInlineStringLiteral(e.Y)
		}
	}
	return false
}

// isEmptyStringLiteral checks if an expression is an empty string literal
func isEmptyStringLiteral(expr ast.Expr) bool {
	lit, ok := expr.(*ast.BasicLit)
	if !ok || lit.Kind != token.STRING {
		return false
	}
	// Remove quotes and check if empty
	return lit.Value == `""` || lit.Value == "``"
}
