package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"embed"
	"encoding/base64"
	"encoding/json"
	"flag"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"time"

	"github.com/gorilla/mux"
	"sigs.k8s.io/yaml"

	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"

	clientset "github.com/giolekva/pcloud/core/nebula/controller/generated/clientset/versioned"
)

var port = flag.Int("port", 8080, "Port to listen on.")
var kubeConfig = flag.String("kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
var masterURL = flag.String("master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
var namespace = flag.String("namespace", "", "Namespace where Nebula CA and Node secrets are stored.")
var caName = flag.String("ca-name", "", "Name of the Nebula CA.")
var configTmpl = flag.String("config-tmpl", "", "Path to the lighthouse configuration template file.")

//go:embed templates/*
var tmpls embed.FS

type Templates struct {
	Index *template.Template
}

func ParseTemplates(fs embed.FS) (*Templates, error) {
	index, err := template.ParseFS(fs, "templates/index.html")
	if err != nil {
		return nil, err
	}
	return &Templates{index}, nil
}

type Handler struct {
	mgr   Manager
	tmpls *Templates
}

func (h *Handler) handleIndex(w http.ResponseWriter, r *http.Request) {
	cas, err := h.mgr.ListAll()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if err := h.tmpls.Index.Execute(w, cas); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func (h *Handler) handleNode(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	namespace := vars["namespace"]
	name := vars["name"]
	qr, err := h.mgr.GetNodeCertQR(namespace, name)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	w.Header().Set("Content-Type", "img/png")
	w.Write(qr)
}

func (h *Handler) handleCA(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	namespace := vars["namespace"]
	name := vars["name"]
	qr, err := h.mgr.GetCACertQR(namespace, name)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	w.Header().Set("Content-Type", "img/png")
	w.Write(qr)
}

type signReq struct {
	Message []byte `json:"message"`
}

type signResp struct {
	Signature []byte `json:"signature"`
}

func (h *Handler) sign(w http.ResponseWriter, r *http.Request) {
	var req signReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	signature, err := h.mgr.Sign(req.Message)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	resp := signResp{
		signature,
	}
	if err := json.NewEncoder(w).Encode(resp); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

type joinReq struct {
	Message   []byte `json:"message"`
	Signature []byte `json:"signature"`
	Name      string `json:"name"`
	PublicKey []byte `json:"public_key"`
	IPCidr    string `json:"ip_cidr"`
}

func (h *Handler) join(w http.ResponseWriter, r *http.Request) {
	var req joinReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	valid, err := h.mgr.VerifySignature(req.Message, req.Signature)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if !valid {
		http.Error(w, "Signature could not be verified", http.StatusBadRequest)
		return
	}
	_, _, err = h.mgr.CreateNode(
		*namespace,
		req.Name,
		*namespace,
		*caName,
		req.IPCidr,
		string(req.PublicKey),
		nil,
	)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	for {
		time.Sleep(1 * time.Second)
		cfg, err := h.mgr.GetNodeConfig(*namespace, req.Name)
		if err != nil {
			fmt.Println(err.Error())
			continue
		}
		cfgBytes, err := yaml.Marshal(cfg)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		cfgB64 := base64.StdEncoding.EncodeToString(cfgBytes)
		if _, err := fmt.Fprint(w, cfgB64); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		break
	}
}

type getResp struct {
	Key   []byte `json:"key"`
	Nonce []byte `json:"nonce"`
	Data  []byte `json:"data"`
}

func (h *Handler) get(w http.ResponseWriter, r *http.Request) {
	fmt.Println("##### GET")
	vars := mux.Vars(r)
	pubKey, err := h.mgr.GetNodeEncryptionPublicKey(*namespace, vars["name"])
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fmt.Println("Got key")
	key := make([]byte, 32)
	if _, err := io.ReadFull(rand.Reader, key); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	block, err := aes.NewCipher(key)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	aesgcm, err := cipher.NewGCM(block)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	nonce := make([]byte, 12)
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		panic(err.Error())
	}
	for {
		time.Sleep(1 * time.Second)
		cfg, err := h.mgr.GetNodeConfig(*namespace, vars["name"])
		if err != nil {
			fmt.Println(err.Error())
			continue
		}
		cfgBytes, err := yaml.Marshal(cfg)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		cfgEnc := aesgcm.Seal(nil, nonce, cfgBytes, nil)
		keyEnc, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, key, []byte(""))
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		nonceEnc, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, nonce, []byte(""))
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		resp := getResp{
			Key:   keyEnc,
			Nonce: nonceEnc,
			Data:  cfgEnc,
		}
		if err := json.NewEncoder(w).Encode(&resp); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		break
	}
}

type approveReq struct {
	EncPublicKey []byte `json:"enc_public_key"`
	Name         string `json:"name"`
	NetPublicKey []byte `json:"net_public_key"`
	IPCidr       string `json:"ip_cidr"`
}

type approveResp struct {
}

func (h *Handler) approve(w http.ResponseWriter, r *http.Request) {
	var req approveReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	_, _, err := h.mgr.CreateNode(
		*namespace,
		req.Name,
		*namespace,
		*caName,
		req.IPCidr,
		string(req.NetPublicKey),
		req.EncPublicKey,
	)
	if err != nil {
		fmt.Println(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

type processCAReq struct {
	Name string `json:"name"`
}

func (h *Handler) processCA(w http.ResponseWriter, r *http.Request) {
	var req processCAReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	ca, err := CreateCertificateAuthority(req.Name)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(ca); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

type processNodeReq struct {
	CAPrivateKey  []byte `json:"ca_private_key"`
	CACert        []byte `json:"ca_certificate"`
	NodeName      string `json:"node_name"`
	NodePublicKey []byte `json:"node_public_key,omitempty"`
	NodeIPCidr    string `json:"node_ip_cidr"`
}

func (h *Handler) processNode(w http.ResponseWriter, r *http.Request) {
	var req processNodeReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	_, ipNet, err := net.ParseCIDR(req.NodeIPCidr)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	node, err := SignNebulaNode(req.CAPrivateKey, req.CACert, req.NodeName, req.NodePublicKey, ipNet)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(node); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func loadConfigTemplate(path string) (map[string]interface{}, error) {
	tmpl, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var m map[string]interface{}
	if err := yaml.Unmarshal(tmpl, &m); err != nil {
		return nil, err
	}
	return m, nil
}

func main() {
	flag.Parse()
	cfgTmpl, err := loadConfigTemplate(*configTmpl)
	if err != nil {
		panic(err)
	}
	cfg, err := clientcmd.BuildConfigFromFlags(*masterURL, *kubeConfig)
	if err != nil {
		panic(err)
	}
	kubeClient, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		panic(err)
	}
	nebulaClient := clientset.NewForConfigOrDie(cfg)
	t, err := ParseTemplates(tmpls)
	if err != nil {
		log.Fatal(err)
	}
	mgr := Manager{
		kubeClient:   kubeClient,
		nebulaClient: nebulaClient,
		namespace:    *namespace,
		caName:       *caName,
		cfgTmpl:      cfgTmpl,
	}
	handler := Handler{
		mgr:   mgr,
		tmpls: t,
	}
	r := mux.NewRouter()
	r.HandleFunc("/api/sign", handler.sign)
	r.HandleFunc("/api/join", handler.join)
	r.HandleFunc("/api/approve", handler.approve)
	r.HandleFunc("/api/get/{name:[a-zA-z0-9-]+}", handler.get)
	r.HandleFunc("/api/process/authority", handler.processCA)
	r.HandleFunc("/api/process/node", handler.processNode)
	r.HandleFunc("/node/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleNode)
	r.HandleFunc("/ca/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleCA)
	r.HandleFunc("/", handler.handleIndex)
	http.Handle("/", r)
	fmt.Printf("Starting HTTP server on port: %d\n", *port)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
}
