package docker

import (
	"archive/tar"
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/go-connections/nat"
	"github.com/moby/moby/client"
	"github.com/spf13/cobra"
)

//RunOption contains some of the options used to create a docker image and run a container
type RunOption struct {
	ImageName      string
	Tag            string
	IP             string
	DockerPort     int
	DockerfilePath string
	//the Script is used to run several commands in entrypoint
	Script      string
	JenkinsPort int
	WarPath     string
	//the TmpDir stores Dockerfile and Script
	TmpDir string
}

//DockerRunOption is an option for starting a container in docker
var DockerRunOption RunOption

//ConnectToDocker returns a client which is used to connect to a local or remote docker host
func (o *RunOption) ConnectToDocker() (cli *client.Client, err error) {
	tcp := fmt.Sprintf("tcp://%s:%d", o.IP, o.DockerPort)
	cli, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation(), client.WithHost(tcp))
	return cli, err
}

//CreateImageAndRunContainer  is to create an docker image and run a container from the image
func (o *RunOption) CreateImageAndRunContainer(cmd *cobra.Command, args []string) (err error) {
	ctx := context.Background()
	cli, err := o.ConnectToDocker()
	if err != nil {
		cmd.Println(err)
		return err
	}
	imageName := o.ImageName + ":" + o.Tag
	err = o.BuildImage(cmd)
	if err != nil {
		cmd.Println(err)
	}
	jenkinsPort, err := nat.NewPort("tcp", "8080")
	if err != nil {
		cmd.Println(err)
	}
	hostConfig := &container.HostConfig{
		PortBindings: nat.PortMap{
			jenkinsPort: []nat.PortBinding{
				{
					HostIP:   "0.0.0.0",
					HostPort: strconv.Itoa(o.JenkinsPort),
				},
			},
		},
	}
	exposedPorts := map[nat.Port]struct{}{
		jenkinsPort: {},
	}
	config := &container.Config{
		Image:        imageName,
		ExposedPorts: exposedPorts,
	}

	resp, err := cli.ContainerCreate(ctx, config, hostConfig, nil, nil, "")
	if err != nil {
		fmt.Println(err)
	}
	cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
	fmt.Println(resp.ID)
	os.Remove(o.TmpDir)
	return nil
}

//BuildImage is used to build an image
func (o *RunOption) BuildImage(cmd *cobra.Command) error {
	ctx := context.Background()
	cli, _ := o.ConnectToDocker()
	dockerFileTarReader, _ := o.GetTarReader(cmd)
	buildOptions := types.ImageBuildOptions{
		Context:    dockerFileTarReader,
		Dockerfile: o.DockerfilePath,
		Remove:     true,
		Tags:       []string{o.ImageName},
	}
	imageBuildResponse, err := cli.ImageBuild(
		ctx,
		dockerFileTarReader,
		buildOptions,
	)

	if err != nil {
		return err
	}
	defer imageBuildResponse.Body.Close()
	buf := new(bytes.Buffer)
	buf.ReadFrom(imageBuildResponse.Body)
	cmd.Println(buf.String())
	return nil
}

//GetTarReader creates tarReader for args in function BuildImage
func (o *RunOption) GetTarReader(cmd *cobra.Command) (*bytes.Reader, error) {
	src := []string{o.DockerfilePath, o.WarPath, o.Script}
	buf := new(bytes.Buffer)
	tw := tar.NewWriter(buf)
	defer tw.Close()

	for _, fileName := range src {
		dockerFileReader, err := os.Open(fileName)
		if err != nil {
			return nil, err
		}
		readDockerFile, err := ioutil.ReadAll(dockerFileReader)
		if err != nil {
			return nil, err
		}
		tarHeader := &tar.Header{
			Name: fileName,
			Size: int64(len(readDockerFile)),
		}
		err = tw.WriteHeader(tarHeader)
		if err != nil {
			return nil, err
		}
		_, err = tw.Write(readDockerFile)
		if err != nil {
			return nil, err
		}
	}
	dockerFileTarReader := bytes.NewReader(buf.Bytes())
	return dockerFileTarReader, nil
}

//CreateDockerfile will create a docker file for running a jenkins war contains plugins hpi
func (o *RunOption) CreateDockerfile(cmd *cobra.Command, args []string) (err error) {
	dir, _ := ioutil.TempDir("", "jenkins-cli")
	o.TmpDir = dir
	sh := "java -Djenkins.install.runSetupWizard=false -jar /usr/share/jenkins/jenkins.war\n" +
		"tail -f /dev/null"
	err = ioutil.WriteFile(dir+"/jenkins-cli-docker.sh", []byte(sh), 0)
	o.Script = dir + "/jenkins-cli-docker.sh"
	if err != nil {
		cmd.Println(err)
	}
	o.DockerfilePath = filepath.Join(dir, "Dockerfile")
	dockerfileString := fmt.Sprintf(`FROM adoptopenjdk/openjdk11
		LABEL Version="1.0-SNAPSHOT"Description="Jenkins formula generated by jcli"Vendor="Chinese Jenkins Community"
		ADD %s /usr/share/jenkins/jenkins.war
		ADD %s /usr/local/bin/jenkins-cli-docker.sh
		ENTRYPOINT ["sh", "/usr/local/bin/jenkins-cli-docker.sh"]`, o.WarPath, o.Script)
	err = ioutil.WriteFile(o.DockerfilePath, []byte(dockerfileString), 0)
	if err != nil {
		cmd.Println(err)
	}
	return nil
}
