package main

/*
	Wrappers.go

	This script provide wrapping functions
	for modules that do not provide
	handler interface within the modules

	--- NOTES ---
	If your module have more than one layer
	or require state keeping, please move
	the abstraction up one layer into
	your own module. Do not keep state on
	the global scope other than single
	Manager instance
*/

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

	"imuslab.com/zoraxy/mod/dynamicproxy"
	"imuslab.com/zoraxy/mod/ipscan"
	"imuslab.com/zoraxy/mod/mdns"
	"imuslab.com/zoraxy/mod/uptime"
	"imuslab.com/zoraxy/mod/utils"
	"imuslab.com/zoraxy/mod/wakeonlan"
)

/*
	Proxy Utils
*/
//Check if site support TLS
func HandleCheckSiteSupportTLS(w http.ResponseWriter, r *http.Request) {
	targetURL, err := utils.PostPara(r, "url")
	if err != nil {
		utils.SendErrorResponse(w, "invalid url given")
		return
	}

	httpsUrl := fmt.Sprintf("https://%s", targetURL)
	httpUrl := fmt.Sprintf("http://%s", targetURL)

	client := http.Client{Timeout: 5 * time.Second}

	resp, err := client.Head(httpsUrl)
	if err == nil && resp.StatusCode == http.StatusOK {
		js, _ := json.Marshal("https")
		utils.SendJSONResponse(w, string(js))
		return
	}

	resp, err = client.Head(httpUrl)
	if err == nil && resp.StatusCode == http.StatusOK {
		js, _ := json.Marshal("http")
		utils.SendJSONResponse(w, string(js))
		return
	}

	utils.SendErrorResponse(w, "invalid url given")
}

/*
	Statistic Summary
*/

// Handle conversion of statistic daily summary to country summary
func HandleCountryDistrSummary(w http.ResponseWriter, r *http.Request) {
	requestClientCountry := map[string]int{}
	statisticCollector.DailySummary.RequestClientIp.Range(func(key, value interface{}) bool {
		//Get this client country of original
		clientIp := key.(string)
		//requestCount := value.(int)

		ci, err := geodbStore.ResolveCountryCodeFromIP(clientIp)
		if err != nil {
			return true
		}

		isoCode := ci.CountryIsoCode
		if isoCode == "" {
			//local or reserved addr
			isoCode = "local"
		}
		uc, ok := requestClientCountry[isoCode]
		if !ok {
			//Create the counter
			requestClientCountry[isoCode] = 1
		} else {
			requestClientCountry[isoCode] = uc + 1
		}
		return true
	})

	js, _ := json.Marshal(requestClientCountry)
	utils.SendJSONResponse(w, string(js))
}

/*
	Up Time Monitor
*/
//Generate uptime monitor targets from reverse proxy rules
func GetUptimeTargetsFromReverseProxyRules(dp *dynamicproxy.Router) []*uptime.Target {
	subds := dp.GetSDProxyEndpointsAsMap()
	vdirs := dp.GetVDProxyEndpointsAsMap()

	UptimeTargets := []*uptime.Target{}
	for subd, target := range subds {
		url := "http://" + target.Domain
		protocol := "http"
		if target.RequireTLS {
			url = "https://" + target.Domain
			protocol = "https"
		}

		UptimeTargets = append(UptimeTargets, &uptime.Target{
			ID:       subd,
			Name:     subd,
			URL:      url,
			Protocol: protocol,
		})
	}

	for vdir, target := range vdirs {
		url := "http://" + target.Domain
		protocol := "http"
		if target.RequireTLS {
			url = "https://" + target.Domain
			protocol = "https"
		}
		UptimeTargets = append(UptimeTargets, &uptime.Target{
			ID:       vdir,
			Name:     "*" + vdir,
			URL:      url,
			Protocol: protocol,
		})
	}

	return UptimeTargets
}

// Handle rendering up time monitor data
func HandleUptimeMonitorListing(w http.ResponseWriter, r *http.Request) {
	if uptimeMonitor != nil {
		uptimeMonitor.HandleUptimeLogRead(w, r)
	} else {
		http.Error(w, "500 - Internal Server Error", http.StatusInternalServerError)
		return
	}
}

// Handle listing current registered mdns nodes
func HandleMdnsListing(w http.ResponseWriter, r *http.Request) {
	js, _ := json.Marshal(previousmdnsScanResults)
	utils.SendJSONResponse(w, string(js))
}

func HandleMdnsScanning(w http.ResponseWriter, r *http.Request) {
	domain, err := utils.PostPara(r, "domain")
	var hosts []*mdns.NetworkHost
	if err != nil {
		//Search for arozos node
		hosts = mdnsScanner.Scan(30, "")
		previousmdnsScanResults = hosts
	} else {
		//Search for other nodes
		hosts = mdnsScanner.Scan(30, domain)
	}

	js, _ := json.Marshal(hosts)
	utils.SendJSONResponse(w, string(js))
}

// handle ip scanning
func HandleIpScan(w http.ResponseWriter, r *http.Request) {
	cidr, err := utils.PostPara(r, "cidr")
	if err != nil {
		//Ip range mode
		start, err := utils.PostPara(r, "start")
		if err != nil {
			utils.SendErrorResponse(w, "missing start ip")
			return
		}

		end, err := utils.PostPara(r, "end")
		if err != nil {
			utils.SendErrorResponse(w, "missing end ip")
			return
		}

		discoveredHosts, err := ipscan.ScanIpRange(start, end)
		if err != nil {
			utils.SendErrorResponse(w, err.Error())
			return
		}

		js, _ := json.Marshal(discoveredHosts)
		utils.SendJSONResponse(w, string(js))
	} else {
		//CIDR mode
		discoveredHosts, err := ipscan.ScanCIDRRange(cidr)
		if err != nil {
			utils.SendErrorResponse(w, err.Error())
			return
		}

		js, _ := json.Marshal(discoveredHosts)
		utils.SendJSONResponse(w, string(js))
	}
}

/*
	WAKE ON LAN

	Handle wake on LAN
	Support following methods
	/?set=xxx&name=xxx Record a new MAC address into the database
	/?wake=xxx Wake a server given its MAC address
	/?del=xxx Delete a server given its MAC address
	/ Default: list all recorded WoL MAC address
*/

func HandleWakeOnLan(w http.ResponseWriter, r *http.Request) {
	set, _ := utils.PostPara(r, "set")
	del, _ := utils.PostPara(r, "del")
	wake, _ := utils.PostPara(r, "wake")
	if set != "" {
		//Get the name of the describing server
		servername, err := utils.PostPara(r, "name")
		if err != nil {
			utils.SendErrorResponse(w, "invalid server name given")
			return
		}

		//Check if the given mac address is a valid mac address
		set = strings.TrimSpace(set)
		if !wakeonlan.IsValidMacAddress(set) {
			utils.SendErrorResponse(w, "invalid mac address given")
			return
		}

		//Store this into the database
		sysdb.Write("wolmac", set, servername)

		utils.SendOK(w)
	} else if wake != "" {
		//Wake the target up by MAC address
		if !wakeonlan.IsValidMacAddress(wake) {
			utils.SendErrorResponse(w, "invalid mac address given")
			return
		}

		log.Println("[WoL] Sending Wake on LAN magic packet to " + wake)
		err := wakeonlan.WakeTarget(wake)
		if err != nil {
			utils.SendErrorResponse(w, err.Error())
			return
		}

		utils.SendOK(w)
	} else if del != "" {
		if !wakeonlan.IsValidMacAddress(del) {
			utils.SendErrorResponse(w, "invalid mac address given")
			return
		}

		sysdb.Delete("wolmac", del)
		utils.SendOK(w)
	} else {
		//List all the saved WoL MAC Address
		entries, err := sysdb.ListTable("wolmac")
		if err != nil {
			utils.SendErrorResponse(w, "unknown error occured")
			return
		}

		type MacAddrRecord struct {
			ServerName string
			MacAddr    string
		}

		results := []*MacAddrRecord{}
		for _, keypairs := range entries {
			macAddr := string(keypairs[0])
			serverName := ""
			json.Unmarshal(keypairs[1], &serverName)

			results = append(results, &MacAddrRecord{
				ServerName: serverName,
				MacAddr:    macAddr,
			})
		}

		js, _ := json.Marshal(results)
		utils.SendJSONResponse(w, string(js))
	}
}

/*
	Zoraxy Host Info
*/

func HandleZoraxyInfo(w http.ResponseWriter, r *http.Request) {
	type ZoraxyInfo struct {
		Version           string
		NodeUUID          string
		Development       bool
		BootTime          int64
		EnableSshLoopback bool
		ZerotierConnected bool
	}

	info := ZoraxyInfo{
		Version:           version,
		NodeUUID:          nodeUUID,
		Development:       development,
		BootTime:          bootTime,
		EnableSshLoopback: *allowSshLoopback,
		ZerotierConnected: ganManager.ControllerID != "",
	}

	js, _ := json.MarshalIndent(info, "", " ")
	utils.SendJSONResponse(w, string(js))
}
