//go:build !no_extra_providers

package cli

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

	"github.com/alecthomas/kingpin/v2"
	"github.com/pkg/errors"

	"github.com/kopia/kopia/repo/blob"
	"github.com/kopia/kopia/repo/blob/sftp"
)

type storageSFTPFlags struct {
	options          sftp.Options
	connectFlat      bool
	embedCredentials bool
}

func (c *storageSFTPFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
	cmd.Flag("path", "Path to the repository in the SFTP/SSH server").Required().StringVar(&c.options.Path)
	cmd.Flag("host", "SFTP/SSH server hostname").Required().StringVar(&c.options.Host)
	cmd.Flag("port", "SFTP/SSH server port").Default("22").IntVar(&c.options.Port)
	cmd.Flag("username", "SFTP/SSH server username").Required().StringVar(&c.options.Username)

	// one of those 3 must be provided
	cmd.Flag("sftp-password", "SFTP/SSH server password").StringVar(&c.options.Password)
	cmd.Flag("keyfile", "path to private key file for SFTP/SSH server").StringVar(&c.options.Keyfile)
	cmd.Flag("key-data", "private key data").StringVar(&c.options.KeyData)

	// one of those 2 must be provided
	cmd.Flag("known-hosts", "path to known_hosts file").StringVar(&c.options.KnownHostsFile)
	cmd.Flag("known-hosts-data", "known_hosts file entries").StringVar(&c.options.KnownHostsData)

	cmd.Flag("embed-credentials", "Embed key and known_hosts in Kopia configuration").BoolVar(&c.embedCredentials)

	cmd.Flag("external", "Launch external passwordless SSH command").BoolVar(&c.options.ExternalSSH)
	cmd.Flag("ssh-command", "SSH command").Default("ssh").StringVar(&c.options.SSHCommand)
	cmd.Flag("ssh-args", "Arguments to external SSH command").StringVar(&c.options.SSHArguments)

	cmd.Flag("flat", "Use flat directory structure").BoolVar(&c.connectFlat)
	cmd.Flag("list-parallelism", "Set list parallelism").Hidden().IntVar(&c.options.ListParallelism)

	commonThrottlingFlags(cmd, &c.options.Limits)
}

func (c *storageSFTPFlags) getOptions(formatVersion int) (*sftp.Options, error) {
	sftpo := c.options

	//nolint:nestif
	if !sftpo.ExternalSSH {
		if c.embedCredentials {
			if sftpo.KeyData == "" {
				d, err := os.ReadFile(sftpo.Keyfile)
				if err != nil {
					return nil, errors.Wrap(err, "unable to read key file")
				}

				sftpo.KeyData = string(d)
				sftpo.Keyfile = ""
			}

			if sftpo.KnownHostsData == "" && sftpo.KnownHostsFile != "" {
				d, err := os.ReadFile(sftpo.KnownHostsFile)
				if err != nil {
					return nil, errors.Wrap(err, "unable to read known hosts file")
				}

				sftpo.KnownHostsData = string(d)
				sftpo.KnownHostsFile = ""
			}
		}

		switch {
		case sftpo.Password != "": // ok

		case sftpo.KeyData != "": // ok

		case sftpo.Keyfile != "":
			a, err := filepath.Abs(sftpo.Keyfile)
			if err != nil {
				return nil, errors.Wrap(err, "error getting absolute path")
			}

			sftpo.Keyfile = a

		default:
			return nil, errors.New("must provide either --sftp-password, --keyfile or --key-data")
		}

		switch {
		case sftpo.KnownHostsData != "": // ok

		case sftpo.KnownHostsFile != "":
			a, err := filepath.Abs(sftpo.KnownHostsFile)
			if err != nil {
				return nil, errors.Wrap(err, "error getting absolute path")
			}

			sftpo.KnownHostsFile = a
		default:
			return nil, errors.New("must provide either --known-hosts or --known-hosts-data")
		}
	}

	sftpo.DirectoryShards = initialDirectoryShards(c.connectFlat, formatVersion)

	return &sftpo, nil
}

func (c *storageSFTPFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
	opt, err := c.getOptions(formatVersion)
	if err != nil {
		return nil, err
	}

	//nolint:wrapcheck
	return sftp.New(ctx, opt, isCreate)
}

func init() {
	mustRegisterStorageProvider(
		"sftp",
		"an SFTP storage",
		func() StorageFlags { return &storageSFTPFlags{} },
	)
}
