package commandconn

import (
	"context"
	"net"
	"net/url"

	"github.com/docker/cli/cli/connhelper"
	"github.com/docker/cli/cli/connhelper/ssh"
	"github.com/docker/cli/cli/context/docker"
	dCliContextStore "github.com/docker/cli/cli/context/store"
	dClient "github.com/docker/docker/client"
	"github.com/pkg/errors"
)

// GetConnectionHelper returns Docker-specific connection helper for the given URL.
// GetConnectionHelper returns nil without error when no helper is registered for the scheme.
//
// ssh://<user>@<host> URL requires Docker 18.09 or later on the remote host.
func GetConnectionHelper(daemonURL string) (*connhelper.ConnectionHelper, error) {
	return getConnectionHelper(daemonURL, []string{"-o ConnectTimeout=5"})
}

func getConnectionHelper(daemonURL string, sshFlags []string) (*connhelper.ConnectionHelper, error) {
	url, err := url.Parse(daemonURL)
	if err != nil {
		return nil, err
	}
	switch scheme := url.Scheme; scheme {
	case "ssh":
		ctxConnDetails, err := ssh.ParseURL(daemonURL)
		if err != nil {
			return nil, errors.Wrap(err, "ssh host connection is not valid")
		}

		return &connhelper.ConnectionHelper{
			Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
				return New(ctx, "ssh", append(sshFlags, ctxConnDetails.Args("docker", "system", "dial-stdio")...)...)
			},
			Host: "http://docker.example.com",
		}, nil
	}
	// Future version may support plugins via ~/.docker/config.json. e.g. "dind"
	// See docker/cli#889 for the previous discussion.
	return nil, err
}

func NewConnectionHelper(daemonURL string) (*connhelper.ConnectionHelper, error) {
	helper, err := GetConnectionHelper(daemonURL)
	if err != nil {
		return nil, err
	}

	return helper, nil
}

func getDockerEndpoint(host string) (docker.Endpoint, error) {
	skipTLSVerify := false
	ep := docker.Endpoint{
		EndpointMeta: docker.EndpointMeta{
			Host:          host,
			SkipTLSVerify: skipTLSVerify,
		},
	}
	// try to resolve a docker client, validating the configuration
	opts, err := ep.ClientOpts()
	if err != nil {
		return docker.Endpoint{}, err
	}
	if _, err := dClient.NewClientWithOpts(opts...); err != nil {
		return docker.Endpoint{}, err
	}
	return ep, nil
}

func GetDockerEndpointMetadataAndTLS(host string) (docker.EndpointMeta, *dCliContextStore.EndpointTLSData, error) {
	ep, err := getDockerEndpoint(host)
	if err != nil {
		return docker.EndpointMeta{}, nil, err
	}
	return ep.EndpointMeta, ep.TLSData.ToStoreTLSData(), nil
}