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

import (
	"context"
	"errors"
	"net/http"
	"os"
	"path"
	"strings"

	"coopcloud.tech/abra/pkg/config"
	contextPkg "coopcloud.tech/abra/pkg/context"
	"coopcloud.tech/abra/pkg/i18n"
	"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 serverName.
func New(serverName string, opts ...Opt) (*client.Client, error) {
	var clientOpts []client.Opt

	ctx, err := GetContext(serverName)
	if err != nil {
		serverDir := path.Join(config.SERVERS_DIR, serverName)
		if _, err := os.Stat(serverDir); err != nil {
			return nil, errors.New(i18n.G("server missing, run \"abra server add %s\"?", serverName))
		}

		// NOTE(p4u1): when the docker context does not exist but the server folder
		//             is there, let's create a new docker context.
		if err = CreateContext(serverName); err != nil {
			return nil, errors.New(i18n.G("server missing context, context creation failed: %s", err))
		}

		ctx, err = GetContext(serverName)
		if err != nil {
			return nil, errors.New(i18n.G("server missing context, run \"abra server add %s\"?", serverName))
		}
	}

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

	var isUnix bool
	if strings.Contains(ctxEndpoint, "unix://") {
		isUnix = true
	}

	if serverName != "default" && !isUnix {
		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,
			},
		}

		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.Debug(i18n.G("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" && !isUnix {
			return cl, errors.New(i18n.G("swarm mode not enabled on %s?", serverName))
		}

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

	return cl, nil
}
