package dind

import (
	"context"
	"fmt"
	"time"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/rancher/rke/docker"
	"github.com/rancher/rke/util"
	"github.com/sirupsen/logrus"
)

const (
	DINDImage           = "docker:24.0.9-dind"
	DINDContainerPrefix = "rke-dind"
	DINDPlane           = "dind"
	DINDNetwork         = "dind-network"
	DINDSubnet          = "172.18.0.0/16"
)

func StartUpDindContainer(ctx context.Context, dindAddress, dindNetwork, dindStorageDriver, dindDNS string) (string, error) {
	cli, err := client.NewEnvClient()
	if err != nil {
		return "", err
	}
	// its recommended to use host's storage driver
	dockerInfo, err := cli.Info(ctx)
	if err != nil {
		return "", err
	}
	storageDriver := dindStorageDriver
	if len(storageDriver) == 0 {
		storageDriver = dockerInfo.Driver
	}

	// Get dind container name
	containerName := fmt.Sprintf("%s-%s", DINDContainerPrefix, dindAddress)
	_, err = cli.ContainerInspect(ctx, containerName)
	if err != nil {
		if !client.IsErrNotFound(err) {
			return "", err
		}
		if err := docker.UseLocalOrPull(ctx, cli, cli.DaemonHost(), DINDImage, DINDPlane, nil); err != nil {
			return "", err
		}
		binds := []string{
			fmt.Sprintf("/var/lib/kubelet-%s:/var/lib/kubelet:shared", containerName),
			"/etc/machine-id:/etc/machine-id:ro",
		}
		isLink, err := util.IsSymlink("/etc/resolv.conf")
		if err != nil {
			return "", err
		}
		if isLink {
			logrus.Infof("[%s] symlinked [/etc/resolv.conf] file detected. Using [%s] as DNS server.", DINDPlane, dindDNS)
		} else {
			binds = append(binds, "/etc/resolv.conf:/etc/resolv.conf")
		}
		imageCfg := &container.Config{
			Image: DINDImage,
			Entrypoint: []string{
				"sh",
				"-c",
				"mount --make-shared / && " +
					"mount --make-shared /sys && " +
					"mount --make-shared /var/lib/docker && " +
					"dockerd-entrypoint.sh --tls=false --storage-driver=" + storageDriver,
			},
			Hostname: dindAddress,
			Env:      []string{"DOCKER_TLS_CERTDIR="},
		}
		hostCfg := &container.HostConfig{
			Privileged: true,
			Binds:      binds,
			// this gets ignored if resolv.conf is bind mounted. So it's ok to have it anyway.
			DNS: []string{dindDNS},
			// Calico needs this
			Sysctls: map[string]string{
				"net.ipv4.conf.all.rp_filter": "1",
			},
		}
		resp, err := cli.ContainerCreate(ctx, imageCfg, hostCfg, nil, nil, containerName)
		if err != nil {
			return "", fmt.Errorf("Failed to create [%s] container on host [%s]: %v", containerName, cli.DaemonHost(), err)
		}

		if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
			return "", fmt.Errorf("Failed to start [%s] container on host [%s]: %v", containerName, cli.DaemonHost(), err)
		}
		logrus.Infof("[%s] Successfully started [%s] container on host [%s]", DINDPlane, containerName, cli.DaemonHost())
		dindContainer, err := cli.ContainerInspect(ctx, containerName)
		if err != nil {
			return "", fmt.Errorf("Failed to get the address of container [%s] on host [%s]: %v", containerName, cli.DaemonHost(), err)
		}
		dindIPAddress := dindContainer.NetworkSettings.IPAddress

		return dindIPAddress, nil
	}
	dindContainer, err := cli.ContainerInspect(ctx, containerName)
	if err != nil {
		return "", fmt.Errorf("Failed to get the address of container [%s] on host [%s]: %v", containerName, cli.DaemonHost(), err)
	}
	dindIPAddress := dindContainer.NetworkSettings.IPAddress
	logrus.Infof("[%s] container [%s] is already running on host[%s]", DINDPlane, containerName, cli.DaemonHost())
	return dindIPAddress, nil
}

func RmoveDindContainer(ctx context.Context, dindAddress string) error {
	cli, err := client.NewEnvClient()
	if err != nil {
		return err
	}
	containerName := fmt.Sprintf("%s-%s", DINDContainerPrefix, dindAddress)
	logrus.Infof("[%s] Removing dind container [%s] on host [%s]", DINDPlane, containerName, cli.DaemonHost())
	_, err = cli.ContainerInspect(ctx, containerName)
	if err != nil {
		if !client.IsErrNotFound(err) {
			return nil
		}
	}

	timeout := 2 * time.Minute
	if err := cli.ContainerStop(ctx, containerName, &timeout); err != nil {
		return fmt.Errorf("Failed to stop dind container [%s] on host [%s]: %v", containerName, cli.DaemonHost(), err)
	}

	logrus.Infof("waiting 1 minute before removing container [%s] on host [%s]", containerName, cli.DaemonHost())
	time.Sleep(1 * time.Minute)

	if err := cli.ContainerRemove(ctx, containerName, types.ContainerRemoveOptions{
		Force:         true,
		RemoveVolumes: true}); err != nil {
		if client.IsErrNotFound(err) {
			logrus.Debugf("[remove/%s] Container doesn't exist on host [%s]", containerName, cli.DaemonHost())
			return nil
		}
		return fmt.Errorf("Failed to remove dind container [%s] on host [%s]: %v", containerName, cli.DaemonHost(), err)
	}
	logrus.Infof("[%s] Successfully Removed dind container [%s] on host [%s]", DINDPlane, containerName, cli.DaemonHost())
	return nil
}
