// Package client provides Docker client initiatialisation functions.
package client

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"os"
	"time"

	contextPkg "coopcloud.tech/abra/pkg/context"
	"coopcloud.tech/abra/pkg/log"
	sshPkg "coopcloud.tech/abra/pkg/ssh"
	commandconnPkg "coopcloud.tech/abra/pkg/upstream/commandconn"
	"github.com/docker/docker/client"
)

// Conf is a Docker client configuration.
type Conf struct {
	Timeout int
}

// Opt is a Docker client option.
type Opt func(c *Conf)

// WithTimeout specifies a timeout for a Docker client.
func WithTimeout(timeout int) Opt {
	return func(c *Conf) {
		c.Timeout = timeout
	}
}

// New initiates a new Docker client. New client connections are validated so
// that we ensure connections via SSH to the daemon can succeed. It takes into
// account that you may only want the local client and not communicate via SSH.
// For this use-case, please pass "default" as the contextName.
func New(serverName string, opts ...Opt) (*client.Client, error) {
	var clientOpts []client.Opt

	if serverName != "default" {
		context, err := GetContext(serverName)
		if err != nil {
			return nil, fmt.Errorf("unknown server, run \"abra server add %s\"?", serverName)
		}

		ctxEndpoint, err := contextPkg.GetContextEndpoint(context)
		if err != nil {
			return nil, err
		}

		conf := &Conf{}
		for _, opt := range opts {
			opt(conf)
		}

		helper, err := commandconnPkg.NewConnectionHelper(ctxEndpoint, conf.Timeout)
		if err != nil {
			return nil, err
		}

		httpClient := &http.Client{
			Transport: &http.Transport{
				DialContext:     helper.Dialer,
				IdleConnTimeout: 30 * time.Second,
			},
		}

		clientOpts = append(clientOpts,
			client.WithHTTPClient(httpClient),
			client.WithHost(helper.Host),
			client.WithDialContext(helper.Dialer),
		)
	}

	version := os.Getenv("DOCKER_API_VERSION")
	if version != "" {
		clientOpts = append(clientOpts, client.WithVersion(version))
	} else {
		clientOpts = append(clientOpts, client.WithAPIVersionNegotiation())
	}

	cl, err := client.NewClientWithOpts(clientOpts...)
	if err != nil {
		return nil, err
	}

	log.Debugf("created client for %s", serverName)

	info, err := cl.Info(context.Background())
	if err != nil {
		return cl, sshPkg.Fatal(serverName, err)
	}

	if info.Swarm.LocalNodeState == "inactive" {
		if serverName != "default" {
			return cl, fmt.Errorf("swarm mode not enabled on %s?", serverName)
		}

		return cl, errors.New("swarm mode not enabled on local server?")
	}

	return cl, nil
}