package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"strings"
)

const (
	IFaceName  = "wg0"
	TunnelPort = 12912
)

var (
	SrvPubKey string
)

func init() {
	// Generate private key
	outPrvK, err := exec.Command("wg", "genkey").Output()
	if err != nil {
		log.Fatal("Unable to generate prvkey:", err)
		return
	}
	outPrvK = bytes.TrimSpace(outPrvK)

	prvFile, err := ioutil.TempFile("", "wg")
	if err != nil {
		log.Fatal("Unable to create tmpPrvFile", err)
		return
	}
	defer os.Remove(prvFile.Name())

	if _, err := prvFile.Write(outPrvK); err != nil {
		log.Fatal(err)
	}
	if err := prvFile.Close(); err != nil {
		log.Fatal(err)
	}

	// Calculate public key
	cmdPubK := exec.Command("wg", "pubkey")
	cmdPubK.Stdin = bytes.NewReader(outPrvK)
	outPubK, err := cmdPubK.Output()
	if err != nil {
		log.Fatal("Unable to calculate pubkey:", err)
		return
	}
	SrvPubKey = string(bytes.TrimSpace(outPubK))

	// Set parameters
	outSet, err := exec.Command("wg", "set", IFaceName, "private-key", prvFile.Name(), "listen-port", strconv.Itoa(TunnelPort)).CombinedOutput()
	if err != nil {
		log.Fatal("Something happend during wg set:", err, string(outSet))
		return
	}

	log.Printf("wg configured: public key:%s", string(outPubK))
}

type PubTunnel struct {
	Login  string   `json:"login"`
	PubKey [][]byte `json:"data"`
	Token  string   `json:"token"`
}

func register(w http.ResponseWriter, r *http.Request) {
	if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
		r.RemoteAddr = addr
	}
	log.Printf("%s \"%s %s\" [%s]\n", r.RemoteAddr, r.Method, r.URL.Path, r.UserAgent())

	// Read the body
	if r.ContentLength < 0 || r.ContentLength > 6553600 {
		http.Error(w, "{errmsg:\"Request too large or request size unknown\"}", http.StatusRequestEntityTooLarge)
		return
	}

	var pt PubTunnel
	if err := json.NewDecoder(r.Body).Decode(&pt); err != nil {
		http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest)
		return
	}

	// Validate wg token
	if j, err := json.Marshal(pt); err != nil {
		http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusInternalServerError)
		return
	} else if r, err := http.NewRequest("POST", "https://adlin.nemunai.re/wg-step", bytes.NewReader(j)); err != nil {
		http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusInternalServerError)
		return
	} else if resp, err := http.DefaultClient.Do(r); err == nil {
		resp.Body.Close()
	} else {
		log.Printf("Unable to register wg-step on token-validator:", err)
	}

	if next_ip, err := findNextIP(); err != nil {
		http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest)
		return
	} else {
		addWgPeer(pt.PubKey[0], next_ip)

		w.Header().Set("Content-Type", "text/plain")
		w.Write([]byte(fmt.Sprintf(`# Address=%s/18

[Peer]
PublicKey = %s
Endpoint = %s:%d
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 15
`, next_ip, SrvPubKey, "172.17.0.15", TunnelPort)))
	}
}

func findNextIP() (next_ip net.IP, err error) {
	var out []byte
	out, err = exec.Command("wg", "show", IFaceName, "allowed-ips").Output()
	if err != nil {
		return
	}

	all_ips := string(out)

	for j := byte(128); j < 190; j++ {
		for i := byte(1); i < 254; i++ {
			if !strings.Contains(all_ips, fmt.Sprintf("172.23.%d.%d/32", j, i)) {
				next_ip = net.IPv4(172, 23, j, i)
				return
			}
		}
	}

	err = errors.New("No IP found")
	return
}

func addWgPeer(cltPubKey []byte, next_ip net.IP) (err error) {
	_, err = exec.Command(
		"wg", "set", IFaceName,
		"peer", base64.StdEncoding.EncodeToString(cltPubKey),
		"allowed-ips", fmt.Sprintf("%s/32", next_ip),
	).Output()
	return
}
