package es

import (
	"bytes"
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io"
	"math/rand"
	"net/http"
	"os"
	"runtime"
	"strconv"
	"time"

	"github.com/docker/docker/api/types/mount"

	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/api/types/image"
	"github.com/docker/go-connections/nat"
	"github.com/pingcap/log"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/db_docker/db/impl"
)

const (
	esVersion = "1.1.0"
)

func StartEsDocker() error {
	for {
		EsPort++
		if impl.IsPortAvailable(EsPort) {
			return startES(EsPort)
		}
		log.Info("port is not available, try another one", zap.Uint16("port", EsPort))
	}
}

func StartKibanaDocker() error {
	for {
		KibanaPort++
		if impl.IsPortAvailable(KibanaPort) {
			return startKibana(KibanaPort)
		}
		log.Info("port is not available, try another one", zap.Uint16("port", KibanaPort))
	}
}

func startES(port uint16) error {
	ctx := context.Background()
	cli, err := impl.GetDockerClient()
	if err != nil {
		return err
	}

	imageName := fmt.Sprintf("opensearchproject/opensearch:%s", esVersion)
	images, err := cli.ImageList(ctx, image.ListOptions{
		All: false,
		Filters: filters.NewArgs(filters.KeyValuePair{
			Key:   "reference",
			Value: imageName,
		}),
	})
	if err != nil {
		return err
	}

	if len(images) < 1 {
		log.Info("image not found, pull image", zap.String("version", esVersion), zap.Uint16("port", port))
		out, err := cli.ImagePull(ctx, imageName, image.PullOptions{})
		if err != nil {
			return nil
		}
		log.Info("pull es image done", zap.String("version", esVersion), zap.Uint16("port", port))
		_, _ = io.Copy(os.Stdout, out)
	}

	hostConfig := &container.HostConfig{
		PortBindings: nat.PortMap{
			"9200/tcp": []nat.PortBinding{
				{
					HostIP:   "127.0.0.1",
					HostPort: fmt.Sprintf("%d", port),
				},
			},
		},
	}
	gid := GetGid()
	containername[gid] = RandString(5)
	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image:        imageName,
		Env:          []string{"discovery.type=single-node", "plugins.security.ssl.http.enabled=false"},
		Labels:       map[string]string{dockerLabelKey: dockerLabelValue},
		ExposedPorts: map[nat.Port]struct{}{nat.Port(fmt.Sprintf("%d/%s", 9200, "tcp")): {}},
	}, hostConfig, nil, nil, containername[gid])
	if err != nil {
		return err
	}

	err = cli.ContainerStart(ctx, resp.ID, container.StartOptions{})
	if err != nil {
		return err
	}

	for !isESReady(port) {
		log.Info("waiting for es", zap.String("version", esVersion), zap.Uint16("port", port))
		time.Sleep(time.Second * 2)
	}

	return nil
}

func startKibana(port uint16) error {
	ctx := context.Background()
	cli, err := impl.GetDockerClient()
	if err != nil {
		return err
	}

	imageName := fmt.Sprintf("opensearchproject/opensearch-dashboards:%s", esVersion)
	images, err := cli.ImageList(ctx, image.ListOptions{
		All: false,
		Filters: filters.NewArgs(filters.KeyValuePair{
			Key:   "reference",
			Value: imageName,
		}),
	})
	if err != nil {
		return err
	}

	if len(images) < 1 {
		log.Info("image not found, pull image", zap.String("version", esVersion), zap.Uint16("port", port))
		out, err := cli.ImagePull(ctx, imageName, image.PullOptions{})
		if err != nil {
			return nil
		}
		log.Info("pull kibana image done", zap.String("version", esVersion), zap.Uint16("port", port))
		_, _ = io.Copy(os.Stdout, out)
	}
	gid := GetGid()
	creatfile()
	hostConfig := &container.HostConfig{
		PortBindings: nat.PortMap{
			"5601/tcp": []nat.PortBinding{
				{
					HostIP:   "127.0.0.1",
					HostPort: fmt.Sprintf("%d", port),
				},
			},
		},
		// Links: []string{"elasticsearch :elasticsearch"},
		Mounts: []mount.Mount{
			{
				Type:   mount.TypeBind,
				Source: "/tmp/opensearch-dashboards.yaml",
				Target: "/usr/share/opensearch-dashboards/config/opensearch_dashboards.yml",
			},
		},
		Links: []string{containername[gid] + ":elasticsearch"},
	}

	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image: imageName,
		// Env:          []string{"DISABLE_SECURITY_DASHBOARDS_PLUGIN=true"},
		Labels:       map[string]string{dockerLabelKey: dockerLabelValue},
		ExposedPorts: map[nat.Port]struct{}{nat.Port(fmt.Sprintf("%d/%s", 5601, "tcp")): {}},
	}, hostConfig, nil, nil, "")
	if err != nil {
		return err
	}

	err = cli.ContainerStart(ctx, resp.ID, container.StartOptions{})
	if err != nil {
		return err
	}

	for !isKibanaReady(port) {
		log.Info("waiting for kibana", zap.String("version", esVersion), zap.Uint16("port", port))
		time.Sleep(time.Second * 2)
	}

	return nil
}

func isKibanaReady(port uint16) bool {
	req, err := http.NewRequest("GET", fmt.Sprintf("http://127.0.0.1:%d%s", port, AwsKibanaPrefix), nil)
	/*req.Header["username"] = []string{"root"}
	req.Header["password"] = []string{"root"}*/if err != nil {
		log.Warn("waiting for es", zap.Error(err))
		return false
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Warn("waiting for es", zap.Error(err))
		return false
	}
	defer resp.Body.Close()

	return resp.StatusCode == http.StatusOK
}

func isESReady(port uint16) bool {
	req, err := http.NewRequest("GET", fmt.Sprintf("http://127.0.0.1:%d", port), nil)
	/*req.Header["username"] = []string{"admin"}
	req.Header["password"] = []string{"admin"}*/if err != nil {
		log.Warn("waiting for es", zap.Error(err))
		return false
	}
	req.SetBasicAuth("admin", "admin")
	tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true, VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
		return nil
	}}}
	client := &http.Client{Transport: tr}
	resp, err := client.Do(req)
	if err != nil {
		log.Warn("waiting for es", zap.Error(err))
		return false
	}
	defer resp.Body.Close()

	return resp.StatusCode == http.StatusOK
}

var (
	EsPort           = uint16(31000 - 1)
	KibanaPort       = uint16(41000 - 1)
	dockerLabelKey   = "component"
	dockerLabelValue = "clinic-test-es-docker"
)

func StopAllESKibanaDocker() {
	fmt.Println("release all es and kibana docker")
	ctx := context.Background()
	cli, err := impl.GetDockerClient()
	if err != nil {
		log.Warn("failed to get the client", zap.Error(err))
	}
	cs, err := cli.ContainerList(ctx, container.ListOptions{
		Filters: filters.NewArgs(filters.KeyValuePair{
			Key:   "label",
			Value: fmt.Sprintf("%s=%s", dockerLabelKey, dockerLabelValue),
		}),
	})
	if err != nil {
		log.Warn("failed to get the client", zap.Error(err))
	}
	fmt.Println("> Clean es and kibana Docker")
	for _, item := range cs {
		fmt.Printf("removing container %s\n", item.ID)
		_ = cli.ContainerRemove(ctx, item.ID, container.RemoveOptions{Force: true})
	}
	fmt.Println("> Clean successful")
}

var (
	strByte       = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
	strByteLen    = len(strByte)
	containername = map[int]string{}
	Dirmap        = map[string]string{}
)

func RandString(length int) string {
	bytes := make([]byte, length)
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < length; i++ {
		bytes[i] = strByte[r.Intn(strByteLen)]
	}
	fmt.Println(string(bytes))
	return string(bytes)
}

func GetGid() (gid int) {
	b := make([]byte, 64)
	b = b[:runtime.Stack(b, false)]
	b = bytes.TrimPrefix(b, []byte("goroutine "))
	b = b[:bytes.IndexByte(b, ' ')]
	n, err := strconv.ParseUint(string(b), 10, 64)
	if err != nil {
		panic(err)
	}
	return int(n)
}

const (
	dashboardsfile = "server.host: '0.0.0.0'\nopensearch.hosts: ['http://elasticsearch:9200']\nopensearch.ssl.verificationMode: none # if not using HTTPS\nopensearch_security.auth.type: basicauth\nopensearch_security.auth.anonymous_auth_enabled: false\nopensearch_security.cookie.secure: false # set to true when using HTTPS\nopensearch_security.cookie.ttl: 3600000\nopensearch_security.session.ttl: 3600000\nopensearch_security.session.keepalive: false\nopensearch_security.readonly_mode.roles: ['kibana_read_only']\nopensearch_security.auth.unauthenticated_routes: []\nopensearch_security.ui.basicauth.login.title: 'Please log in using your user name and password'\nopensearch.username: \"admin\"\nopensearch.password: \"admin\"\nopensearch.requestHeadersWhitelist: [authorization, securitytenant,security_tenant]\nserver.basePath: \"/kibana\"\nserver.rewriteBasePath: true\nopensearch_security.multitenancy.enabled: true\nopensearch_security.multitenancy.tenants.enable_global: true\nopensearch_security.multitenancy.tenants.enable_private: true\nopensearch_security.multitenancy.tenants.preferred: [\"Private\", \"Global\"]\nopensearch_security.multitenancy.enable_filter: false"
)

func creatfile() {
	file, err := os.Create("/tmp/opensearch-dashboards.yaml")
	if err != nil {
		panic(err)
	}
	_, err = file.WriteString(dashboardsfile)
	if err != nil {
		panic(err)
	}
}
