package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
	"sync"

	"github.com/pingcap/errors"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

var gardenerConfig string
var baseConfig string
var infraapiConfig string
var addr string

var projectName string

func init() {
	flag.StringVar(&gardenerConfig, "gardener-config", "", "gardener_config path")
	flag.StringVar(&baseConfig, "base-config", "", "base config path")
	flag.StringVar(&infraapiConfig, "infra-api-config", "", "infra-api config path")
	flag.StringVar(&projectName, "projectName", "nightly", "projectName@dbaas used to deploy")
	flag.StringVar(&addr, "addr", ":8080", "http addr")
}

func buildClient(kubePath string) (client.Client, *kubernetes.Clientset, error) {
	cfg, err := clientcmd.BuildConfigFromFlags("", kubePath)
	if err != nil {
		return nil, nil, errors.AddStack(err)
	}

	c, err := client.New(cfg, client.Options{})
	if err != nil {
		return nil, nil, errors.AddStack(err)
	}

	clientset, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		return nil, nil, errors.AddStack(err)
	}

	return c, clientset, nil
}

// logs returns a handler func to response the logs of pods with specific labels.
func logs(client *kubernetes.Clientset, namespace string, labelMap map[string]string) http.HandlerFunc {
	return func(w http.ResponseWriter, request *http.Request) {
		log.Printf("recv request %v", request.URL.Path)
		ctx := request.Context()

		list, err := client.CoreV1().Pods(namespace).List(
			ctx,
			metav1.ListOptions{
				LabelSelector: labels.SelectorFromSet(labelMap).String(),
			},
		)
		if err != nil {
			http.Error(w, err.Error(), 500)
			return
		}

		// var sinceSeconds int64 = -3600 * 1000
		podLogOpts := &corev1.PodLogOptions{
			// SinceSeconds: &sinceSeconds,
		}

		var wg sync.WaitGroup
		wg.Add(len(list.Items))

		log.Printf("get %d pods by %s in ns %q", len(list.Items), labelMap, namespace)

		bufs := make(chan *bytes.Buffer, len(list.Items))

		for _, pod := range list.Items {
			pod := pod
			log.Printf("pod: %s", pod.GetName())
			go func() {
				defer wg.Done()

				buf := bytes.NewBuffer(nil)
				buf.WriteString("logs of: " + pod.GetName())
				buf.WriteString("\n\n")

				req := client.CoreV1().Pods(namespace).GetLogs(pod.GetName(), podLogOpts)
				podLogs, err := req.Stream(ctx)
				if err != nil {
					buf.WriteString(err.Error())
					return
				}
				defer podLogs.Close()

				_, err = io.Copy(buf, podLogs)
				if err != nil {
					buf.WriteString(err.Error())
					return
				}

				bufs <- buf
			}()
		}

		go func() {
			wg.Wait()
			close(bufs)
		}()

		for buf := range bufs {
			_, err := io.Copy(w, buf)
			if err != nil {
				log.Printf("failed to write %v", err)
			}
		}

	}
}

func run() error {
	baseClient, baseCS, err := buildClient(baseConfig)
	if err != nil {
		return err
	}

	gardClient, gardCS, err := buildClient(gardenerConfig)
	if err != nil {
		return err
	}

	_ = baseClient
	_ = gardClient
	_ = gardCS

	_, sharedCS, err := getContextConfig(".*f01.*")
	if err != nil && err != ErrNotFoundContext {
		return err
	}

	http.HandleFunc(
		"/logs/infra-api/infra-provider",
		logs(baseCS, "infra", map[string]string{"app": "infra-provider"}),
	)

	http.HandleFunc(
		"/logs/dbaas/worker",
		logs(baseCS, projectName, map[string]string{"app": "worker"}),
	)
	http.HandleFunc(
		"/logs/dbaas/central-server",
		logs(baseCS, projectName, map[string]string{"app": "central-server"}),
	)

	if sharedCS != nil {
		http.HandleFunc(
			"/logs/dbaas/shared-provider",
			logs(sharedCS, "tidb-admin", map[string]string{"app": "aws-shared-provider-manager"}),
		)

	}

	link := func(url string, text string) string {
		if !strings.HasPrefix(url, "http://") {
			url = "http://" + url
		}
		return fmt.Sprintf("<a href=\"%s\">%s</a>", url, text)
	}

	br := "<br>"

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		lk := link(r.Host+"/logs/infra-api/infra-provider", "/logs/infra-api/infra-provider")
		fmt.Fprintln(w, lk)
		fmt.Fprintln(w, br)

		lk = link(r.Host+"/logs/dbaas/worker", "/logs/dbaas/worker")
		fmt.Fprintln(w, lk)
		fmt.Fprintln(w, br)

		lk = link(r.Host+"/logs/dbaas/central-server", "/logs/dbaas/central-server")
		fmt.Fprintln(w, lk)
		fmt.Fprintln(w, br)

		lk = link(r.Host+"/logs/dbaas/shared-provider", "/logs/infra-api/shared-provider")
		fmt.Fprintln(w, lk)
		fmt.Fprintln(w, br)
	})

	log.Println("http serve at ", addr)
	err = http.ListenAndServe(addr, nil)
	return err
}

func main() {
	flag.Parse()

	if gardenerConfig == "" || baseConfig == "" {
		flag.Usage()
		os.Exit(-1)
	}

	err := run()
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

var ErrNotFoundContext = errors.New("not found")

// getContextConfig get the specify context config.
// will get kubeconfig from recommended env or file(~/.kube/config).
func getContextConfig(ctxReg string) (client.Client, *kubernetes.Clientset, error) {
	reg := regexp.MustCompile(ctxReg)

	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
	// if you want to change the loading rules (which files in which order), you can do so here

	apiconfig, err := loadingRules.Load()
	if err != nil {
		return nil, nil, errors.AddStack(err)
	}

	var matchName string
	for name := range apiconfig.Contexts {
		if reg.MatchString(name) {
			matchName = name
			break
		}
	}

	if matchName == "" {
		return nil, nil, ErrNotFoundContext
	}

	configOverrides := &clientcmd.ConfigOverrides{
		CurrentContext: matchName,
	}

	kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
	cfg, err := kubeConfig.ClientConfig()
	if err != nil {
		return nil, nil, errors.AddStack(err)
	}

	log.Println("detect match context ", matchName, cfg.Host)

	c, err := client.New(cfg, client.Options{})
	if err != nil {
		return nil, nil, errors.AddStack(err)
	}

	clientset, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		return nil, nil, errors.AddStack(err)
	}

	return c, clientset, nil
}
