package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net"
	"strconv"
	"strings"
	"time"

	"net/http"

	"tplink/internal/tplink"

	"github.com/prometheus/client_golang/prometheus/promhttp"
)

func httpAccessLog(req *http.Request) {
	log.Printf("[TRACE] %s - %s - %s\n", req.Method, req.RemoteAddr, req.RequestURI)
}

func crossSiteOrigin(w http.ResponseWriter) {
	w.Header().Add("Access-Control-Allow-Origin", "*")
	w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
}

func httpServer(host string, port int) {
	path := http.NewServeMux()

	connection := &http.Server{
		Addr:         host + ":" + strconv.FormatInt(int64(port), 10),
		Handler:      path,
		ReadTimeout:  time.Duration(config.WebSrvReadTimeout) * time.Second,
		WriteTimeout: time.Duration(config.WebSrvWriteTimeout) * time.Second,
		IdleTimeout:  time.Duration(config.WebSrvIdleTimeout) * time.Second,
	}

	// metrics
	path.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
		httpAccessLog(r)
		crossSiteOrigin(w)
		promhttp.Handler().ServeHTTP(w, r)
	})
	// api
	path.HandleFunc("/api/v1/tplink", webTPLink)
	// healthcheck
	path.HandleFunc("/healthcheck", webHealthCheck)
	// root
	path.HandleFunc("/", webRoot)

	if err := connection.ListenAndServe(); err != nil {
		log.Fatalf("[ERROR] %s\n", err)
	}
}

func webRoot(w http.ResponseWriter, r *http.Request) {
	httpAccessLog(r)
	crossSiteOrigin(w)

	if strings.ToLower(r.Method) == "get" {
		tmpltWebRoot(w)
	} else {
		log.Printf("[DEBUG] Request to '/' was made using the wrong method: expected %s, got %s\n", "GET", strings.ToUpper(r.Method))
		tmpltError(w, http.StatusBadRequest, "Invalid http method.")
	}
}

func webHealthCheck(w http.ResponseWriter, r *http.Request) {
	httpAccessLog(r)
	crossSiteOrigin(w)

	if strings.ToLower(r.Method) == "get" {
		tmpltHealthCheck(w)
	} else {
		log.Printf("[DEBUG] Request to '/healthcheck' was made using the wrong method: expected %s, got %s\n", "GET", strings.ToUpper(r.Method))
		tmpltError(w, http.StatusBadRequest, "Invalid http method.")
	}
}

func webTPLink(w http.ResponseWriter, r *http.Request) {
	httpAccessLog(r)
	crossSiteOrigin(w)

	if strings.ToLower(r.Method) == "get" {
		keys := r.URL.Query()
		if len(keys) == 0 {
			log.Printf("[INFO] Required parameters missing: no host, state, or deviceid was provided.\n")
			tmpltError(w, http.StatusBadRequest, "Required parameters missing: no parameter for host, state, or deviceid was provided.")
			return
		}

		var (
			host     string
			hostSet  bool
			state    bool
			stateSet bool
			id       int
		)

		for k, v := range keys {
			log.Printf("%s: %v\n", k, v)
			switch strings.ToLower(k) {
			case "host":
				if len(v) != 1 {
					log.Printf("[INFO] Host was defined multiple times.\n")
					tmpltError(w, http.StatusBadRequest, "Required parameter host was defined multiple times.")
					return
				}
				_, err := net.LookupHost(v[0])
				if err != nil {
					log.Printf("[INFO] Unable to resolve provided hostname: %s\n", v[0])
					tmpltError(w, http.StatusBadRequest, "Unable to resolve provided hostname.")
					return
				}
				hostSet = true
				host = v[0]

			case "state":
				if len(v) != 1 {
					log.Printf("[INFO] State was defined multiple times.\n")
					tmpltError(w, http.StatusBadRequest, "Required parameter state was defined multiple times.")
					return
				}
				s, err := strconv.ParseBool(v[0])
				if err != nil {
					log.Printf("[INFO] State is not boolean: %s\n", v[0])
					tmpltError(w, http.StatusBadRequest, "Required parameter state is not boolean.")
					return
				}
				stateSet = true
				state = s

			case "deviceid":
				if len(v) != 1 {
					log.Printf("[INFO] Deviceid was defined multiple times.\n")
					tmpltError(w, http.StatusBadRequest, "Required parameter device was defined multiple times.")
					return
				}
				d, err := strconv.Atoi(v[0])
				if err != nil {
					log.Printf("[INFO] Deviceid is not an integer: %s\n", v[0])
					tmpltError(w, http.StatusBadRequest, "Required parameter deviceid is not an integer.")
					return
				}
				id = d
			}
		}

		if !hostSet {
			log.Printf("[INFO] Required parameter missing: no host was provided.\n")
			tmpltError(w, http.StatusBadRequest, "Required parameters missing: no parameter for host provided.")
			return
		}
		if !stateSet {
			log.Printf("[INFO] Required parameter missing: no state was provided.\n")
			tmpltError(w, http.StatusBadRequest, "Required parameters missing: no parameter for state provided.")
			return
		}

		t := tplink.Tplink{
			Host:     host,
			SwitchID: id,
		}
		if err := t.ChangeStateMultiSwitch(state); err != nil {
			log.Printf("[INFO] Unable to change device state: %v\n", err)
			tmpltError(w, http.StatusInternalServerError, fmt.Sprintf("Unable to change device state: %v\n", err))
			return
		}

		sysInfo, err := t.SystemInfo()
		if err != nil {
			log.Printf("[INFO] Unable to get info from target device: %v\n", err)
			tmpltError(w, http.StatusInternalServerError, fmt.Sprintf("Unable to read info from device: %v\n", err))
			return
		}

		var ds int
		if len(sysInfo.System.GetSysinfo.Children) == 0 {
			ds = sysInfo.System.GetSysinfo.RelayState
		} else {
			ds = sysInfo.System.GetSysinfo.Children[id].State
		}

		var fs string
		if ds == 0 {
			fs = "OFF"
		} else {
			fs = "ON"
		}

		o := struct {
			Status   string `json:"status" yaml:"status"`
			Host     string `json:"host" yaml:"host"`
			DeviceID int    `json:"device_id" yaml:"deviceID"`
		}{
			Status:   fs,
			Host:     host,
			DeviceID: id,
		}
		w.Header().Add("Content-Type", "application/json")

		output, err := json.MarshalIndent(o, "", "  ")
		if err != nil {
			log.Printf("[TRACE] Unable to marshal error message: %v.", err)
		}
		w.Write(output) //nolint:errcheck

	} else if strings.ToLower(r.Method) == "options" {
		return
	} else {
		log.Printf("[DEBUG] Request to '%s' was made using the wrong method: expected %s, got %s", "GET|OPTIONS", r.URL.Path, strings.ToUpper(r.Method))
		tmpltError(w, http.StatusBadRequest, "Invalid http method.")
	}
}