package action

import (
	"context"
	"fmt"

	"github.com/gopasspw/gopass/internal/action/exit"
	"github.com/gopasspw/gopass/internal/backend"
	"github.com/gopasspw/gopass/internal/config"
	"github.com/gopasspw/gopass/internal/cui"
	"github.com/gopasspw/gopass/internal/out"
	"github.com/gopasspw/gopass/pkg/ctxutil"
	"github.com/gopasspw/gopass/pkg/debug"
	"github.com/gopasspw/gopass/pkg/fsutil"
	"github.com/gopasspw/gopass/pkg/termio"
	"github.com/urfave/cli/v2"
)

const logo = `
   __     _    _ _      _ _   ___   ___
 /'_ '\ /'_'\ ( '_'\  /'_' )/',__)/',__)
( (_) |( (_) )| (_) )( (_| |\__, \\__, \
'\__  |'\___/'| ,__/''\__,_)(____/(____/
( )_) |       | |
 \___/'       (_)
`

// IsInitialized returns an error if the store is not properly
// prepared.
func (s *Action) IsInitialized(c *cli.Context) error {
	ctx := ctxutil.WithGlobalFlags(c)
	inited, err := s.Store.IsInitialized(ctx)
	if err != nil {
		return exit.Error(exit.Unknown, err, "Failed to initialize store: %s", err)
	}

	if inited {
		debug.Log("Store is fully initialized and ready to go\n\nAll systems go. 🚀\n")
		name := c.Args().First()
		// setting the mount point here is not enough when we're using the REPL mode
		ctx = config.WithMount(ctx, s.Store.MountPoint(name))
		s.printReminder(ctx)
		if c.Command.Name != "sync" && !c.Bool("nosync") {
			_ = s.autoSync(ctx)
		}

		return nil
	}

	debug.Log("Store needs to be initialized.\n\nAbort. Abort. Abort. 🚫\n")
	if !ctxutil.IsInteractive(ctx) {
		return exit.Error(exit.NotInitialized, nil, "password-store is not initialized. Try '%s init'", s.Name)
	}

	out.Printf(ctx, logo)
	out.Printf(ctx, "🌟 Welcome to gopass!")
	out.Noticef(ctx, "No existing configuration found.")

	contSetup, err := termio.AskForBool(ctx, "❓ Do you want to continue to setup?", false)
	if err != nil {
		return err
	}
	if contSetup {
		return s.Setup(c)
	}

	out.Printf(ctx, "☝ Please run 'gopass setup'")

	return exit.Error(exit.NotInitialized, err, "not initialized")
}

// Init a new password store with a first gpg id.
func (s *Action) Init(c *cli.Context) error {
	ctx := ctxutil.WithGlobalFlags(c)
	path := c.String("path")
	alias := c.String("store")

	ctx, err := initParseContext(ctx, c)
	if err != nil {
		return err
	}
	out.Printf(ctx, "🍭 Initializing a new password store ...")

	if name := termio.DetectName(c.Context, c); name != "" {
		ctx = ctxutil.WithUsername(ctx, name)
	}

	if email := termio.DetectEmail(c.Context, c); email != "" {
		ctx = ctxutil.WithEmail(ctx, email)
	}

	inited, err := s.Store.IsInitialized(ctx)
	if err != nil {
		return exit.Error(exit.Unknown, err, "Failed to initialized store: %s", err)
	}

	if inited {
		out.Errorf(ctx, "Store is already initialized!")
	}

	if err := s.init(ctx, alias, path, c.Args().Slice()...); err != nil {
		return exit.Error(exit.Unknown, err, "Failed to initialize store: %s", err)
	}

	return nil
}

func initParseContext(ctx context.Context, c *cli.Context) (context.Context, error) {
	if c.IsSet("crypto") {
		var err error
		ctx, err = backend.WithCryptoBackendString(ctx, c.String("crypto"))
		if err != nil {
			return ctx, exit.Error(exit.Unknown, err, "Failed to set crypto backend: %s", err)
		}
	}

	if c.IsSet("storage") {
		var err error
		ctx, err = backend.WithStorageBackendString(ctx, c.String("storage"))
		if err != nil {
			return ctx, exit.Error(exit.Unknown, err, "Failed to set storage backend: %s", err)
		}
	}

	if !backend.HasCryptoBackend(ctx) {
		debug.Log("Using default Crypto Backend (GPGCLI)")
		ctx = backend.WithCryptoBackend(ctx, backend.GPGCLI)
	}

	if !backend.HasStorageBackend(ctx) {
		debug.Log("Using default storage backend (GitFS)")
		ctx = backend.WithStorageBackend(ctx, backend.GitFS)
	}

	sb := backend.GetStorageBackend(ctx)
	if sb == backend.CryptFS {
		out.Warning(ctx, "⚠ CryptFS is an experimental backend. Use at your own risk! ⚠")
	}
	if sb == backend.JJFS {
		out.Warning(ctx, "⚠ JJFS is an experimental backend. Use at your own risk! ⚠")
	}

	return ctx, nil
}

func (s *Action) init(ctx context.Context, alias, path string, keys ...string) error {
	if path == "" {
		if alias != "" {
			path = config.PwStoreDir(alias)
		} else {
			path = s.Store.Path()
		}
	}
	path = fsutil.CleanPath(path)

	debug.Log("Initializing Store %q in %q for %+v", alias, path, keys)

	out.Printf(ctx, "🔑 Searching for usable private Keys ...")
	debug.Log("Checking private keys for: %+v", keys)
	crypto := s.getCryptoFor(ctx, alias)

	// private key selection doesn't matter for plain. save one question.
	// TODO should ask the backend
	if crypto.Name() == "plain" {
		keys, _ = crypto.ListIdentities(ctx)
	}

	if len(keys) < 1 {
		if crypto.Name() != "age" {
			out.Notice(ctx, "Hint: Use 'gopass init <subkey> to use subkeys!'")
		}
		nk, err := cui.AskForPrivateKey(ctx, crypto, "🎮 Please select a private key for encrypting secrets:")
		if err != nil {
			out.Noticef(ctx, "Hint: Use 'gopass setup --crypto %s' to be guided through an initial setup instead of 'gopass init'", crypto.Name())

			return fmt.Errorf("failed to read user input: %w", err)
		}
		keys = []string{nk}
	}

	debug.Log("Initializing sub store - Alias: %q - Path: %q - Keys: %+v", alias, path, keys)
	if err := s.Store.Init(ctx, alias, path, keys...); err != nil {
		return fmt.Errorf("failed to init store %q at %q: %w", alias, path, err)
	}

	if alias != "" && path != "" {
		debug.Log("Mounting sub store %q -> %q", alias, path)
		if err := s.Store.AddMount(ctx, alias, path); err != nil {
			return fmt.Errorf("failed to add mount %q: %w", alias, err)
		}
	}

	if backend.HasStorageBackend(ctx) {
		bn := backend.StorageBackendName(backend.GetStorageBackend(ctx))
		debug.Log("Initializing RCS (%s) ...", bn)
		if err := s.rcsInit(ctx, alias, ctxutil.GetUsername(ctx), ctxutil.GetEmail(ctx)); err != nil {
			debug.Log("Stacktrace: %+v\n", err)
			out.Errorf(ctx, "❌ Failed to init Version Control (%s): %s", bn, err)
		}
		debug.Log("RCS initialized as %s", s.Store.Storage(ctx, alias).Name())
	} else {
		debug.Log("not initializing RCS backend ...")
	}

	out.Printf(ctx, "🏁 Password store %s initialized for:", path)
	s.printRecipients(ctx, alias)

	return nil
}

func (s *Action) printRecipients(ctx context.Context, alias string) {
	crypto := s.Store.Crypto(ctx, alias)
	for _, recipient := range s.Store.ListRecipients(ctx, alias) {
		if kl, err := crypto.FindRecipients(ctx, recipient); err == nil && len(kl) > 0 {
			recipient = crypto.FormatKey(ctx, kl[0], "")
		}
		out.Printf(ctx, "📩 "+recipient)
	}
}

func (s *Action) getCryptoFor(ctx context.Context, name string) backend.Crypto {
	return s.Store.Crypto(ctx, name)
}
