package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/docopt/docopt-go"
	"github.com/go-ini/ini"
	"github.com/tidwall/gjson"
)

var (
	appVersion             string
	buildTime              string
	CertBase               string
	KeyBase                string
	GroupName              string
	RedisBaseURL           string
	VaultBaseURL           string
	certificateDestination string
	fullchainDestination   string
	keyDestination         string
	caDestination          string
	Type                   string
)

// get redis key
func GetRedisKey(redisurl string, redistoken string) string {
	client := &http.Client{}

	req, err := http.NewRequest("GET", redisurl, nil)
	req.SetBasicAuth("redis", redistoken)
	resp, err := client.Do(req)
	body, err := ioutil.ReadAll(resp.Body)
	defer resp.Body.Close()

	if err != nil {
		log.Fatalf("[ERROR] Fail to read %v: %v", redisurl, err)
	}
	return fmt.Sprintf(string(body))
}

// create directory structure and write certificate to file
func WriteToFile(content string, destination string, groupname string, filemode os.FileMode, dirmode os.FileMode) {
	baseDir := filepath.Dir(destination)
	if _, err := os.Stat(baseDir); os.IsNotExist(err) {
		os.MkdirAll(baseDir, 0755)
	}
	os.Chmod(baseDir, dirmode)

	file, err := os.OpenFile(destination, os.O_WRONLY|os.O_CREATE, filemode)
	if err != nil {
		log.Fatalf("[ERROR] %v cannot be created", destination)
	}

	fmt.Fprintf(file, "%v\n", content)
	file.Close()
}

// ReadOSRelease from /etc/os-release
func ReadOSRelease(configfile string) map[string]string {
	cfg, err := ini.Load(configfile)
	if err != nil {
		log.Fatal("[ERROR] Fail to read file: ", err)
	}

	ConfigParams := make(map[string]string)
	ConfigParams["ID"] = cfg.Section("").Key("ID").String()

	return ConfigParams
}

func main() {

	OSInfo := ReadOSRelease("/etc/os-release")
	OSRelease := OSInfo["ID"]
	if OSRelease == "centos" || OSRelease == "rhel" {
		CertBase = "/etc/pki/tls/certs"
		KeyBase = "/etc/pki/tls/private"
		GroupName = "root"
	} else if OSRelease == "ubuntu" || OSRelease == "debian" {
		CertBase = "/etc/ssl/certs"
		KeyBase = "/etc/ssl/private"
		GroupName = "ssl-cert"
	} else if OSRelease == "arch" {
		CertBase = "/etc/ssl/certs"
		KeyBase = "/etc/ssl/private"
		GroupName = "root"
	} else {
		log.Fatalf("don't know what to do with OS: %v", OSRelease)
	}

	usage := fmt.Sprintf(`ACME Downloader:
  - fetches and stores a given Certificate, Full Chain, CA and Private Key

Usage:
  acme-downloader --redis-token=REDISTOKEN --vault-token=VAULTTOKEN --cert-name=CERTNAME --team-name=TEAMNAME [--days=DAYS] [--type=TYPE] [--cert-destination=CERTDESTINATION] [--fullchain-destination=FULLCHAINDESTINATION] [--key-destination=KEYDESTINATION] [--ca-destination=CADESTINATION]
  acme-downloader -v | --version
  acme-downloader -b | --build
  acme-downloader -h | --help

Options:
  -h --help                                     Show this screen
  -v --version                                  Print version exit
  -b --build                                    Print version and build information and exit
  --redis-token=REDISTOKEN                      Redis access token
  --vault-token=VAULTTOKEN                      Vault access token
  --cert-name=CERTNAME                          Certificate name
  --team-name=TEAMNAME                          Team name: swd, dream_team, it, ne, ti...
  --days=DAYS                                   Days before expiration [default: 30]
  --type=TYPE                                   Type, EV or OV [default: EV]
  --cert-destination=CERTDESTINATION            Cert Destination [default: %v/<cert-name>.crt]
  --fullchain-destination=FULLCHAINDESTINATION  Full Chain Destination[default: %v/<cert-name>_fullchain.crt]
  --key-destination=KEYDESTINATION              Key Destination [default: %v/<cert-name>.key]
  --ca-destination=CADESTINATION                CA Destination [default: %v/COMODO_<type>.crt]
`, CertBase, CertBase, KeyBase, CertBase)

	// Annoyingly docopt tries to use 'version' the way he wants and I am using build

	arguments, _ := docopt.Parse(usage, nil, true, appVersion, false)

	if arguments["--build"] == true {
		fmt.Printf("acme-downloader version: %v, built on: %v\n", appVersion, buildTime)
		os.Exit(0)
	}

	VaultToken := arguments["--vault-token"].(string)
	CertName := arguments["--cert-name"].(string)
	CertNameUndercored := strings.Replace(CertName, ".", "_", -1)
	TeamName := arguments["--team-name"].(string)
	RedisToken := arguments["--redis-token"].(string)
	Type = arguments["--type"].(string)
	RedisBaseURL = "https://redis.geant.org/GET"
	VaultBaseURL = "https://vault.geant.org/v1"
	VaultURL := fmt.Sprintf("%v/%v/%v/vault_%v_key", VaultBaseURL, TeamName, CertName, CertNameUndercored)
	RedisCertURL := fmt.Sprintf("%v/%v:%v:redis_%v_pem.txt", RedisBaseURL, TeamName, CertName, CertNameUndercored)
	RedisCAURL := fmt.Sprintf("%v/%v:%v:redis_%v_chain_pem.txt", RedisBaseURL, TeamName, CertName, CertNameUndercored)
	RedisFullChainURL := fmt.Sprintf("%v/%v:%v:redis_%v_fullchain_pem.txt", RedisBaseURL, TeamName, CertName, CertNameUndercored)

	certificate := GetRedisKey(RedisCertURL, RedisToken)
	ca := GetRedisKey(RedisCAURL, RedisToken)
	fullChain := GetRedisKey(RedisFullChainURL, RedisToken)

	if arguments["--cert-destination"] == fmt.Sprintf("%v/<cert-name>.crt", CertBase) {
		certificateDestination = fmt.Sprintf("%v/%v.crt", CertBase, CertName)
	} else {
		certificateDestination = arguments["--cert-destination"].(string)
	}
	if arguments["--fullchain-destination"] == fmt.Sprintf("%v/<cert-name>_fullchain.crt", CertBase) {
		fullchainDestination = fmt.Sprintf("%v/%v_fullchain.crt", CertBase, CertName)
	} else {
		fullchainDestination = arguments["--fullchain-destination"].(string)
	}
	if arguments["--key-destination"] == fmt.Sprintf("%v/<cert-name>.key", KeyBase) {
		keyDestination = fmt.Sprintf("%v/%v.key", KeyBase, CertName)
	} else {
		keyDestination = arguments["--key-destination"].(string)
	}
	if arguments["--ca-destination"] == fmt.Sprintf("%v/COMODO_<type>.crt", CertBase) {
		caDestination = fmt.Sprintf("%v/COMODO_%v.crt", CertBase, Type)
	} else {
		caDestination = arguments["--ca-destination"].(string)
	}

	// get Vault key
	vaultClient := &http.Client{}
	vaultReq, err := http.NewRequest("GET", VaultURL, nil)
	vaultReq.Header.Add("X-vault-token", VaultToken)
	vaultResp, err := vaultClient.Do(vaultReq)
	vaultBody, err := ioutil.ReadAll(vaultResp.Body)
	defer vaultResp.Body.Close()
	if err != nil {
		log.Fatalf("Fail to read %v: %v", VaultURL, err)
	}
	privKey := gjson.Get(string(vaultBody), "data.value").String()

	WriteToFile(certificate, certificateDestination, GroupName, 0644, 0755)
	WriteToFile(fullChain, fullchainDestination, GroupName, 0644, 0755)
	WriteToFile(ca, caDestination, GroupName, 0644, 0755)
	WriteToFile(privKey, keyDestination, GroupName, 0640, 0750)

	fmt.Printf("installed: %v\n", certificateDestination)
	fmt.Printf("installed: %v\n", caDestination)
	fmt.Printf("installed: %v\n", fullchainDestination)
	fmt.Printf("installed: %v\n", keyDestination)

}