package main

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/spf13/viper"
	"gopkg.in/yaml.v2"
)

type webhook struct {
	Name              string
	Path              string
	SecretKey         string `yaml:"secret_key"`
	NumSwitches       int    `yaml:"num_switches"`
	MomentarySwitches []int  `yaml:"momentary_switches"`
	SwitchStates      []int  `json:switch_states`
}

func debug(msg string, args ...interface{}) {
	if viper.GetBool("Debug") {
		log.Printf(msg, args...)
	}
}

func initWebhooks(filename string) {
	bytes, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Panicf("Couldn't read config file: %s", err)
	}

	var hooks []webhook

	err = yaml.Unmarshal(bytes, &hooks)
	if err != nil {
		log.Panicf("Couldn't parse webhook file: %v", err)
	}

	debug("Webhook definitions:")
	debug("%v", hooks)

	for _, v := range hooks {
		v.SwitchStates = make([]int, v.NumSwitches)
		http.HandleFunc("/"+v.Path, makeWebhookHandler(v))
	}
}

func runServer() {
	http.ListenAndServe(":"+viper.GetString("ListenPort"), nil)
}

func makeWebhookHandler(hook webhook) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		// authentication check
		keys, ok := r.URL.Query()["key"]
		if !ok || len(keys) == 0 {
			log.Printf("No key found for webhook: %s", hook.Name)
			http.NotFound(w, r)
			return
		}
		providedKey := keys[0]

		if providedKey == "" || providedKey != hook.SecretKey {
			log.Printf("Failed to authenticate request for webhook: %s", hook.Name)
			debug("Got key '%s', expected key '%s'", providedKey, hook.SecretKey)
			http.NotFound(w, r)
			return
		}

		// now actually handle the request
		if r.Method == "GET" {
			webhookRead(hook, w, r)

		} else if r.Method == "POST" {
			webhookWrite(hook, w, r)
		}
	}
}

func webhookRead(hook webhook, w http.ResponseWriter, r *http.Request) {
	err := json.NewEncoder(w).Encode(hook.SwitchStates)

	if err != nil {
		log.Printf("Failed to send response: %v", err)
		return
	}

	// momentary switches have been read, so make sure they're 0
	// next time
	for _, i := range hook.MomentarySwitches {
		hook.SwitchStates[i] = 0
	}
}

func webhookWrite(hook webhook, w http.ResponseWriter, r *http.Request) {
	err := json.NewDecoder(r.Body).Decode(&hook.SwitchStates)
	if err != nil {
		log.Printf("Failed to parse json for '%s': %v", hook.Name, err)
		http.Error(w, "Failed to parse json", http.StatusBadRequest)
		return
	}
}

func main() {
	// read environment config
	viper.BindEnv("Debug", "DEBUG")
	viper.BindEnv("WebhookFile", "WEBHOOK_CONFIG_FILE")
	viper.BindEnv("ListenPort", "PORT")

	viper.SetDefault("ListenPort", "7200")
	viper.SetDefault("WebhookFile", "./webhooks.yaml")
	viper.SetDefault("Debug", false)

	// read webhook config
	initWebhooks(viper.GetString("WebhookFile"))

	// serve app
	runServer()
}
