package client

import (
	"errors"
	"fmt"

	command "github.com/docker/cli/cli/command"
	dConfig "github.com/docker/cli/cli/config"
	context "github.com/docker/cli/cli/context"
	"github.com/docker/cli/cli/context/docker"
	contextStore "github.com/docker/cli/cli/context/store"
	"github.com/moby/term"
	"github.com/sirupsen/logrus"
)

type Context = contextStore.Metadata

func CreateContext(contextName string, user string, port string) error {
	host := contextName
	if user != "" {
		host = fmt.Sprintf("%s@%s", user, host)
	}
	if port != "" {
		host = fmt.Sprintf("%s:%s", host, port)
	}
	host = fmt.Sprintf("ssh://%s", host)
	if err := createContext(contextName, host); err != nil {
		return err
	}
	logrus.Debugf("created the '%s' context", contextName)
	return nil
}

// createContext interacts with Docker Context to create a Docker context config
func createContext(name string, host string) error {
	s := NewDefaultDockerContextStore()
	contextMetadata := contextStore.Metadata{
		Endpoints: make(map[string]interface{}),
		Name:      name,
	}

	contextTLSData := contextStore.ContextTLSData{
		Endpoints: make(map[string]contextStore.EndpointTLSData),
	}

	dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(host)
	if err != nil {
		return err
	}

	contextMetadata.Endpoints[docker.DockerEndpoint] = dockerEP
	if dockerTLS != nil {
		contextTLSData.Endpoints[docker.DockerEndpoint] = *dockerTLS
	}

	if err := s.CreateOrUpdate(contextMetadata); err != nil {
		return err
	}

	if err := s.ResetTLSMaterial(name, &contextTLSData); err != nil {
		return err
	}

	return nil
}

func DeleteContext(name string) error {
	if name == "default" {
		return errors.New("context 'default' cannot be removed")
	}

	if _, err := GetContext(name); err != nil {
		return err
	}

	// remove any context that might be loaded
	// TODO: Check if the context we are removing is the active one rather than doing it all the time
	cfg := dConfig.LoadDefaultConfigFile(nil)
	cfg.CurrentContext = ""
	if err := cfg.Save(); err != nil {
		return err
	}

	return NewDefaultDockerContextStore().Remove(name)
}

func GetContext(contextName string) (contextStore.Metadata, error) {
	ctx, err := NewDefaultDockerContextStore().GetMetadata(contextName)
	if err != nil {
		return contextStore.Metadata{}, err
	}

	return ctx, nil
}

func GetContextEndpoint(ctx contextStore.Metadata) (string, error) {
	// safe to use docker key hardcoded since abra doesn't use k8s... yet...
	endpointmeta, ok := ctx.Endpoints["docker"].(context.EndpointMetaBase)
	if !ok {
		err := errors.New("context lacks Docker endpoint")
		return "", err
	}
	return endpointmeta.Host, nil
}

func newContextStore(dir string, config contextStore.Config) contextStore.Store {
	return contextStore.New(dir, config)
}

func NewDefaultDockerContextStore() *command.ContextStoreWithDefault {
	// Grabbing the stderr from Docker commands
	// Much easier to fit this into the code we are using to replicate docker cli commands
	_, _, stderr := term.StdStreams()
	// TODO: Look into custom docker configs in case users want that
	dockerConfig := dConfig.LoadDefaultConfigFile(stderr)
	contextDir := dConfig.ContextStoreDir()
	storeConfig := command.DefaultContextStoreConfig()
	store := newContextStore(contextDir, storeConfig)

	dockerContextStore := &command.ContextStoreWithDefault{
		Store: store,
		Resolver: func() (*command.DefaultContext, error) {
			// nil for the Opts because it works without it and its a cli thing
			return command.ResolveDefaultContext(nil, dockerConfig, storeConfig, stderr)
		},
	}
	return dockerContextStore
}