package daemon

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/docker/go-connections/tlsconfig"
	dockerclient "github.com/moby/moby/client"
	"go.podman.io/image/v5/types"
)

// NewDockerClient initializes a new API client based on the passed SystemContext.
func newDockerClient(sys *types.SystemContext) (*dockerclient.Client, error) {
	host := dockerclient.DefaultDockerHost
	if sys != nil && sys.DockerDaemonHost != "" {
		host = sys.DockerDaemonHost
	}

	opts := []dockerclient.Opt{
		dockerclient.WithHost(host),
	}

	// We conditionalize building the TLS configuration only to TLS sockets:
	//
	// The dockerclient.Client implementation differentiates between
	// - Client.proto, which is ~how the connection is establishe (IP / AF_UNIX/Windows)
	// - Client.scheme, which is what is sent over the connection (HTTP with/without TLS).
	//
	// Only Client.proto is set from the URL in dockerclient.WithHost(),
	// Client.scheme is detected based on a http.Client.TLSClientConfig presence;
	// dockerclient.WithHTTPClient with a client that has TLSClientConfig set
	// will, by default, trigger an attempt to use TLS.
	//
	// So, don’t use WithHTTPClient for unix:// sockets at all.
	//
	// Similarly, if we want to communicate over plain HTTP on a TCP socket (http://),
	// we also should not set TLSClientConfig.  We continue to use WithHTTPClient
	// with our slightly non-default settings to avoid a behavior change on updates of c/image.
	//
	// Alternatively we could use dockerclient.WithScheme to drive the TLS/non-TLS logic
	// explicitly, but we would still want to set WithHTTPClient (differently) for https:// and http:// ;
	// so that would not be any simpler.
	serverURL, err := dockerclient.ParseHostURL(host)
	if err != nil {
		return nil, err
	}
	switch serverURL.Scheme {
	case "unix": // Nothing
	case "npipe": // Nothing
	case "http":
		hc := httpConfig()
		opts = append(opts, dockerclient.WithHTTPClient(hc))
	default:
		hc, err := tlsConfig(sys)
		if err != nil {
			return nil, err
		}
		opts = append(opts, dockerclient.WithHTTPClient(hc))
	}

	return dockerclient.New(opts...)
}

func tlsConfig(sys *types.SystemContext) (*http.Client, error) {
	// This is intended to follow github.com/moby/moby/client.defaultHTTPClient
	// and approximately github.com/moby/moby/client.WithTLSClientConfigFromEnv;
	// we can’t use WithTLSClientConfig because 1) it uses ExclusiveRootPools:true,
	// and 2) there is no clean way to override the crypto choices of tlsconfig.Client.
	// So, we need to load the certs and keys ourselves in the BaseTLSConfig case
	// anyway, we might just as well do it in the other case as well.

	var tlsConfig *tls.Config
	if sys != nil && sys.BaseTLSConfig != nil {
		tlsConfig = sys.BaseTLSConfig.Clone()
	} else {
		tlsConfig = &tls.Config{
			// As of 2025-08, tlsconfig.ClientDefault() differs from Go 1.23 defaults only in CipherSuites;
			// so, limit us to only using that value. If go-connections/tlsconfig changes its policy, we
			// will want to consider that and make a decision whether to follow suit.
			// There is some chance that eventually the Go default will be to require TLS 1.3, and that point
			// we might want to drop the dependency on go-connections entirely.
			CipherSuites: tlsconfig.ClientDefault().CipherSuites,
		}
	}

	if sys != nil {
		if sys.DockerDaemonInsecureSkipTLSVerify {
			tlsConfig.InsecureSkipVerify = true
		} else if sys.DockerDaemonCertPath != "" {
			caFilePath := filepath.Join(sys.DockerDaemonCertPath, "ca.pem")
			caData, err := os.ReadFile(caFilePath)
			if err != nil {
				return nil, fmt.Errorf("reading CA file %q: %w", caFilePath, err)
			}
			caPool, err := x509.SystemCertPool()
			if err != nil {
				return nil, fmt.Errorf("reading systemwide CAs: %w", err)
			}
			if !caPool.AppendCertsFromPEM(caData) {
				return nil, fmt.Errorf("no certificate found in %q", caFilePath)
			}
			tlsConfig.RootCAs = caPool
		}

		if sys.DockerDaemonCertPath != "" {
			cert, err := tls.LoadX509KeyPair(
				filepath.Join(sys.DockerDaemonCertPath, "cert.pem"),
				filepath.Join(sys.DockerDaemonCertPath, "key.pem"))
			if err != nil {
				return nil, fmt.Errorf("loading X509 key pair: %w", err)
			}
			tlsConfig.Certificates = []tls.Certificate{cert}
		}
	}

	return &http.Client{
		Transport: &http.Transport{
			Proxy:           http.ProxyFromEnvironment,
			TLSClientConfig: tlsConfig,
			// In general we want to follow github.com/moby/moby/client.defaultHTTPClient , as long as it doesn’t affect compatibility.
			// These idle connection limits really only apply to long-running clients, which is not our case here;
			// we include the same values purely for symmetry.
			MaxIdleConns:    6,
			IdleConnTimeout: 30 * time.Second,
		},
		CheckRedirect: dockerclient.CheckRedirect,
	}, nil
}

func httpConfig() *http.Client {
	return &http.Client{
		Transport: &http.Transport{
			Proxy:           http.ProxyFromEnvironment,
			TLSClientConfig: nil,
			// In general we want to follow github.com/moby/moby/client.defaultHTTPClient , as long as it doesn’t affect compatibility.
			// These idle connection limits really only apply to long-running clients, which is not our case here;
			// we include the same values purely for symmetry.
			MaxIdleConns:    6,
			IdleConnTimeout: 30 * time.Second,
		},
		CheckRedirect: dockerclient.CheckRedirect,
	}
}
