package docker

import (
	"errors"
	"strings"

	"bitbucket.org/robert2206/automation/modules/settings"
	docker "github.com/fsouza/go-dockerclient"
)

var (
	// errorContainerRemove is a dispatched error when container cannot be removed
	errorContainerRemove = errors.New("container cannot be removed")
)

// Image represent a docker image
type Image struct {
	ID   string
	Tag  string
	Size int64
}

// Network represent a network ip used by docker
type Network struct {
	ID string
	IP string
}

// Port represent a port user by docker container
type Port struct {
	IP          string
	Type        string
	PublicPort  int64
	PrivatePort int64
}

// Container represent docker container
type Container struct {
	ID       string
	Name     string
	Image    string
	State    string
	Ports    []*Port
	Networks map[string]*Network
}

// Create a container
func Create(name string, image string) (*Container, error) {
	cli, err := getDockerClient()

	if err != nil {
		return nil, err
	}

	ctr, err := cli.CreateContainer(docker.CreateContainerOptions{
		Name: name,
		Config: &docker.Config{
			Image:      image,
			Memory:     102400,
			MemorySwap: 102400,
		},
	})

	if err != nil {
		return nil, err
	}

	return &Container{
		ID:    ctr.ID,
		Name:  ctr.Name,
		Image: ctr.Image,
		State: ctr.State.Status,
	}, nil
}

// Remove a container
func Remove(idOrName string) error {
	cli, err := getDockerClient()

	if err != nil {
		return err
	}

	ctr := GetContainer(idOrName)

	if ctr == nil {
		return errorContainerRemove
	}

	return cli.RemoveContainer(docker.RemoveContainerOptions{
		ID:            ctr.ID,
		RemoveVolumes: true,
	})
}

// Start container
func Start(idOrName string) error {
	cli, err := getClientFor(idOrName)

	if err != nil {
		return err
	}

	return cli.StartContainer(idOrName, nil)
}

// Restart container
func Restart(idOrName string) error {
	cli, err := getClientFor(idOrName)

	if err != nil {
		return err
	}

	return cli.RestartContainer(idOrName, 3)
}

// Stop container
func Stop(idOrName string) error {
	cli, err := getClientFor(idOrName)

	if err != nil {
		return err
	}

	return cli.StopContainer(idOrName, 3)
}

// GetAllImages returns all docker images
func GetAllImages() ([]*Image, error) {
	cli, err := getDockerClient()

	if err != nil {
		return nil, err
	}

	imgs, err := cli.ListImages(docker.ListImagesOptions{
		All: true,
	})

	if err != nil {
		return nil, err
	}

	results := make([]*Image, len(imgs))

	for i, img := range imgs {
		results[i] = &Image{
			ID:   img.ID,
			Tag:  img.RepoTags[0],
			Size: img.Size,
		}
	}

	return results, nil
}

// GetAllContainers returns all docker containers
func GetAllContainers() ([]*Container, error) {
	cli, err := getDockerClient()

	if err != nil {
		return nil, err
	}

	ctrs, err := cli.ListContainers(docker.ListContainersOptions{
		All: true,
	})

	if err != nil {
		return nil, err
	}

	results := make([]*Container, len(ctrs))

	for i, ctr := range ctrs {
		results[i] = &Container{
			ID:    ctr.ID,
			Name:  ctr.Names[0],
			Image: ctr.Image,
			State: ctr.State,
			Ports: func() []*Port {
				prts := make([]*Port, len(ctr.Ports))

				for j, prt := range ctr.Ports {
					prts[j] = &Port{
						IP:          prt.IP,
						Type:        prt.Type,
						PublicPort:  prt.PublicPort,
						PrivatePort: prt.PrivatePort,
					}
				}

				return prts
			}(),
			Networks: func() map[string]*Network {
				netws := make(map[string]*Network, len(ctr.Networks.Networks))

				for k, netw := range ctr.Networks.Networks {
					netws[k] = &Network{
						ID: netw.NetworkID,
						IP: netw.IPAddress,
					}
				}

				return netws
			}(),
		}
	}

	return results, nil
}

// GetContainer return container matched by id or name
func GetContainer(idOrName string) *Container {
	ctrs, err := GetAllContainers()

	if err != nil {
		return nil
	}

	return func() *Container {
		for _, ctr := range ctrs {
			if ctr.ID == idOrName || ctr.Name == idOrName {
				return ctr
			}
		}

		return nil
	}()
}

// GetAllPublicPorts get all public exposed ports
func GetAllPublicPorts() ([]int64, error) {
	ctrs, err := GetAllContainers()

	if err != nil {
		return nil, err
	}

	return func() []int64 {
		var prts []int64

		for _, ctr := range ctrs {
			for _, ptr := range ctr.Ports {
				prts = append(prts, ptr.PublicPort)
			}
		}

		return prts
	}(), nil
}

// GetNetwork get network used by container
func GetNetwork(idOrName string) {

}

// GetPrivateIP get private ip used by container
func GetPrivateIP(idOrName string) {

}

func getClientFor(idOrName string) (*docker.Client, error) {
	err := checkIdOrName(idOrName)

	if err != nil {
		return nil, err
	}

	cli, err := getDockerClient()

	if err != nil {
		return nil, err
	}

	return cli, nil
}

func checkIdOrName(idOrName string) error {
	if len(strings.TrimSpace(idOrName)) == 0 {
		return errors.New("container id or name not provided")
	}

	return nil
}

func getDockerClient() (*docker.Client, error) {
	endpoint := settings.Ctx.DockerSockDir

	if strings.HasSuffix(endpoint, ".sock") {
		endpoint = strings.Join([]string{"unix:", endpoint}, "/")
	}

	return docker.NewClient(endpoint)
}
