package action

import (
	"context"
	"fmt"
	"os"
	"strings"

	"github.com/gopasspw/gopass/internal/action/exit"
	"github.com/gopasspw/gopass/internal/out"
	"github.com/gopasspw/gopass/internal/tpl"
	"github.com/gopasspw/gopass/pkg/ctxutil"
	"github.com/gopasspw/gopass/pkg/gopass"
	"github.com/urfave/cli/v3"
)

// secretGetter is the minimal store interface required by the template engine.
type secretGetter interface {
	Get(context.Context, string) (gopass.Secret, error)
}

// pathRestrictedStore wraps a secretGetter and only allows Get() calls for
// secret names that have one of the configured path prefixes. All other
// accesses are denied with a generic error so untrusted templates cannot
// exfiltrate secrets outside the declared scope.
type pathRestrictedStore struct {
	inner      secretGetter
	allowPaths []string
}

func (p *pathRestrictedStore) Get(ctx context.Context, name string) (gopass.Secret, error) {
	for _, prefix := range p.allowPaths {
		if strings.HasPrefix(name, prefix) {
			return p.inner.Get(ctx, name)
		}
	}

	return nil, fmt.Errorf("access denied: %q is not within an allowed path", name)
}

// Process is a command to process a template and replace secrets contained in it.
func (s *miscHandler) Process(ctx context.Context, cmd *cli.Command) error {
	ctx = ctxutil.WithGlobalFlags(ctx, cmd)
	file := cmd.Args().First()
	if file == "" {
		return exit.Error(exit.Usage, nil, "Usage: %s process <FILE>", s.Name)
	}

	allowPaths := cmd.StringSlice("allow-path")

	buf, err := os.ReadFile(file)
	if err != nil {
		return exit.Error(exit.IO, err, "Failed to read file: %s", file)
	}

	// Decide which store view the template engine may access.
	var store secretGetter
	if len(allowPaths) > 0 {
		store = &pathRestrictedStore{inner: s.Store, allowPaths: allowPaths}
	} else {
		out.Warningf(ctx, "No --allow-path flag set. The template has unrestricted access to ALL secrets in the store. Only process templates from trusted sources.")
		store = s.Store
	}

	obuf, err := tpl.Execute(ctx, string(buf), file, nil, store)
	if err != nil {
		return exit.Error(exit.IO, err, "Failed to process file: %s", file)
	}

	out.Print(ctx, string(obuf))

	return nil
}
