package age

import (
	"context"
	"errors"
	"os"
	"path/filepath"
	"strings"

	"filippo.io/age"
	"filippo.io/age/agessh"
	"github.com/gopasspw/gopass/pkg/ctxutil"
	"golang.org/x/crypto/ssh"
)

var sshCache map[string]age.Identity

// getSSHIdentities returns all SSH identities available for the current user.
func (a *Age) getSSHIdentities(ctx context.Context) (map[string]age.Identity, error) {
	if sshCache != nil {
		return sshCache, nil
	}

	uhd, err := os.UserHomeDir()
	if err != nil {
		return nil, err
	}

	sshDir := filepath.Join(uhd, ".ssh")
	files, err := os.ReadDir(sshDir)
	if err != nil {
		return nil, err
	}

	ids := make(map[string]age.Identity, len(files))
	for _, file := range files {
		fn := filepath.Join(sshDir, file.Name())
		if !strings.HasSuffix(fn, ".pub") {
			continue
		}

		recp, id, err := a.parseSSHIdentity(ctx, fn)
		if err != nil {
			// debug.Log("Failed to parse SSH identity %s: %s", fn, err)
			continue
		}

		// debug.Log("parsed SSH identity %s from %s", recp, fn)
		ids[recp] = id
	}
	sshCache = ids

	return ids, nil
}

// parseSSHIdentity parses a SSH public key file and returns the recipient and the identity.
func (a *Age) parseSSHIdentity(ctx context.Context, pubFn string) (string, age.Identity, error) {
	privFn := strings.TrimSuffix(pubFn, ".pub")
	_, err := os.Stat(privFn)
	if err != nil {
		return "", nil, err
	}

	pubBuf, err := os.ReadFile(pubFn)
	if err != nil {
		return "", nil, err
	}

	privBuf, err := os.ReadFile(privFn)
	if err != nil {
		return "", nil, err
	}

	pubkey, _, _, _, err := ssh.ParseAuthorizedKey(pubBuf) //nolint:dogsled
	if err != nil {
		return "", nil, err
	}

	recp := strings.TrimSuffix(string(ssh.MarshalAuthorizedKey(pubkey)), "\n")
	id, err := agessh.ParseIdentity(privBuf)
	if err != nil {
		// handle encrypted SSH identities here.
		var perr *ssh.PassphraseMissingError
		if errors.As(err, &perr) {
			id, err := agessh.NewEncryptedSSHIdentity(pubkey, privBuf, func() ([]byte, error) {
				return ctxutil.GetPasswordCallback(ctx)(pubFn, false)
			})

			return recp, id, err
		}

		return "", nil, err
	}

	return recp, id, nil
}
