package main

import (
	"APIServer/cnf"
	"APIServer/ews"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"text/template"
	"time"

	"github.com/chai2010/winsvc"
	_ "github.com/go-sql-driver/mysql"
	"github.com/natefinch/lumberjack"
	uuid "github.com/satori/go.uuid"
	yaml "gopkg.in/yaml.v2"
)

var cfg = cnf.Cfg{}
var svc string = "apisvr"
var vsn bool = false
var ver string = ""
var logger *log.Logger
var flagIns bool = false
var flagRmv bool = false
var flagRun bool = false
var flagTer bool = false
var flagRst bool = false

func init() {
	flag.BoolVar(&flagIns, "i", false, "Install Service")
	flag.BoolVar(&flagRmv, "u", false, "Uninstall Service")
	flag.BoolVar(&flagRun, "s", false, "Start Service")
	flag.BoolVar(&flagTer, "t", false, "Stop Service")
	flag.BoolVar(&flagRst, "r", false, "Restart Service")
	flag.BoolVar(&vsn, "v", false, "Show Program Version")
	flag.Parse()

	logger = log.New(&lumberjack.Logger{
		Filename:   strings.TrimSuffix(os.Args[0], ".exe") + ".log",
		MaxAge:     30,
		MaxSize:    10,
		MaxBackups: 100,
		LocalTime:  true,
	}, "", log.Ldate|log.Ltime|log.Lmicroseconds)
}

func main() {
	if vsn {
		showVersion()
		os.Exit(0)
	}

	self := strings.TrimSuffix(os.Args[0], ".exe")
	file := self + ".yml"
	src, err := os.ReadFile(file)
	if err != nil {
		log.Printf("[ERR]  %s\r\n", err.Error())
		logger.Printf("[ERR]  %s\r\n", err.Error())
		os.Exit(1)
	}
	err = yaml.Unmarshal(src, &cfg)
	if err != nil {
		log.Printf("[ERR]  %s\r\n", err.Error())
		logger.Printf("[ERR]  %s\r\n", err.Error())
		os.Exit(1)
	}

	cwd, err := winsvc.GetAppPath()
	if err != nil {
		log.Printf("[ERR]  %s\r\n", err.Error())
		logger.Printf("[ERR]  %s\r\n", err.Error())
		os.Exit(1)
	}
	err = os.Chdir(filepath.Dir(cwd))
	if err != nil {
		log.Printf("[ERR]  %s\r\n", err.Error())
		logger.Printf("[ERR]  %s\r\n", err.Error())
		os.Exit(1)
	}

	// Install Service
	if flagIns {
		err := winsvc.InstallService(cwd, svc, "API Server")
		if err != nil {
			log.Printf("[ERR]  Install Service Failed: %s\r\n", err.Error())
			logger.Printf("[ERR]  Install Service Failed: %s\r\n", err.Error())
			os.Exit(1)
		}
		log.Printf("[MSG]  Install Service Successfully.\r\n")
		logger.Printf("[MSG]  Install Service Successfully.\r\n")
		return
	}

	// Uninstall Service
	if flagRmv {
		err := winsvc.RemoveService(svc)
		if err != nil {
			log.Printf("[ERR]  Uninstall Service Failed: %s\r\n", err.Error())
			logger.Printf("[ERR]  Uninstall Service Failed: %s\r\n", err.Error())
			os.Exit(1)
		}
		log.Printf("[MSG]  Uninstall Service Successfully.\r\n")
		logger.Printf("[MSG]  Uninstall Service Successfully.\r\n")
		return
	}

	// Start Service
	if flagRun {
		err := winsvc.StartService(svc)
		if err != nil {
			log.Printf("[ERR]  Start Service Failed: %s\r\n", err.Error())
			logger.Printf("[ERR]  Start Service Failed: %s\r\n", err.Error())
			os.Exit(1)
		}
		log.Printf("[MSG]  Service Started.\r\n")
		logger.Printf("[MSG]  Service Started.\r\n")
		return
	}

	// Stop Service
	if flagTer {
		err := winsvc.StopService(svc)
		if err != nil {
			log.Printf("[ERR]  Stop Service Failed: %s\r\n", err.Error())
			logger.Printf("[ERR]  Stop Service Failed: %s\r\n", err.Error())
			os.Exit(1)
		}
		log.Printf("[MSG]  Service Stop.\r\n")
		logger.Printf("[MSG]  Service Stop.\r\n")
		return
	}

	// Restart Service
	if flagRst {
		err := winsvc.StopService(svc)
		if err != nil {
			log.Printf("[ERR]  Stop Service Failed: %s\r\n", err.Error())
			logger.Printf("[ERR]  Stop Service Failed: %s\r\n", err.Error())
			os.Exit(1)
		}
		log.Printf("[MSG]  Service Stop.\r\n")
		logger.Printf("[MSG]  Service Stop.\r\n")

		time.Sleep(time.Second * 1)
		err = winsvc.StartService(svc)
		if err != nil {
			log.Printf("[ERR]  Start Service Failed: %s\r\n", err.Error())
			logger.Printf("[ERR]  Start Service Failed: %s\r\n", err.Error())
			os.Exit(1)
		}
		log.Printf("[MSG]  Service Started.\r\n")
		logger.Printf("[MSG]  Service Started.\r\n")
		return
	}

	if !winsvc.InServiceMode() {
		err := winsvc.RunAsService(svc, startSvc, stopSvc, false)
		if err != nil {
			log.Printf("[ERR]  Run As Service Failed: %s\r\n", err.Error())
			logger.Printf("[ERR]  Run As Service Failed: %s\r\n", err.Error())
			os.Exit(1)
		}
		log.Printf("[MSG]  Run As Service ...\r\n")
		logger.Printf("[MSG]  Run As Service ...\r\n")
	}
}

func startSvc() {
	log.Printf("[MSG]  Starting Service ...\r\n")
	logger.Printf("[MSG]  Starting Service ...\r\n")
	http.HandleFunc("/", handleIndex)
	http.HandleFunc("/request/", handleRequest)
	http.HandleFunc("/fileinfo/", handleFileInfo)
	http.HandleFunc("/setuutinfo/", handleSetUutInfo)
	http.HandleFunc("/getuutinfo/", handleGetUutInfo)
	logger.Printf("[MSG]  Starting HTTP Server On Port: %s\r\n", cfg.Settings.ListenPort)
	err := http.ListenAndServe(":"+cfg.Settings.ListenPort, nil)
	if err != nil {
		logger.Printf("%s\r\n", err.Error())
		os.Exit(1)
	}
}

func stopSvc() {
	log.Printf("[MSG]  Stopping Service ...\r\n")
	logger.Printf("[MSG]  Stopping Service ...\r\n")
	os.Exit(0)
}

func handleIndex(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	if r.RequestURI == "/favicon.ico" {
		return
	}
	if r.Method != http.MethodGet {
		w.WriteHeader(http.StatusMethodNotAllowed)
		fmt.Fprintf(w, "MethodNotAllowed: %v\r\n", http.StatusMethodNotAllowed)
		return
	}

	tpl, err := template.ParseFiles("./Index.html")
	if err != nil {
		w.WriteHeader(http.StatusMethodNotAllowed)
		fmt.Fprintf(w, "ErrMsg: %v\r\n", err)
		return
	}
	err = tpl.Execute(w, nil)
	if err != nil {
		w.WriteHeader(http.StatusMethodNotAllowed)
		fmt.Fprintf(w, "ErrMsg: %v\r\n", err)
		return
	}
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	if r.RequestURI == "/favicon.ico" {
		return
	}
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		fmt.Fprintf(w, "MethodNotAllowed: %v\r\n", http.StatusMethodNotAllowed)
		return
	}

	reqID := uuid.NewV4().String()
	logger.Printf("RequestID: %s;  Addr: %s;  URI: %s\r\n", reqID, r.RemoteAddr, r.RequestURI)

	if err := r.ParseForm(); err != nil {
		logger.Printf("RequestID: %s;  ErrMsg: %s\r\n", reqID, err.Error())
		return
	}

	var params []string
	if len(r.Form) > 0 {
		logger.Printf("RequestID: %s;  Form: %#v\r\n", reqID, r.Form)
		for k, v := range r.Form {
			if cfg.Settings.ParamCheck {
				if !chkParams(k) {
					continue
				}
				if !chkParams(v[0]) {
					continue
				}
			}
			params = append(params, fmt.Sprintf("%s:%s", k, strings.ReplaceAll(v[0], " ", "_")))
		}
	}

	logger.Printf("RequestID: %s;  Params: %#v\r\n", reqID, params)
	resp := runCommand(cfg.Settings.HookScript, params, reqID)
	fmt.Fprintf(w, "%s\r\n", resp)
}

func handleFileInfo(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	if r.RequestURI == "/favicon.ico" {
		return
	}

	reqID := uuid.NewV4().String()
	logger.Printf("RequestID: %s;  Addr: %s;  URI: %s\r\n", reqID, r.RemoteAddr, r.RequestURI)

	if err := r.ParseForm(); err != nil {
		logger.Printf("RequestID: %s;  ErrMsg: %s\r\n", reqID, err.Error())
		return
	}

	var path string = ""
	if len(r.Form) > 0 {
		for k, v := range r.Form {
			if cfg.Settings.ParamCheck {
				if !chkParams(k) {
					continue
				}
				if !chkParams(v[0]) {
					continue
				}
			}
			if k == "path" {
				path = v[0]
			}
		}
	}

	if path == "" {
		fmt.Fprintf(w, "%v", http.StatusBadRequest)
		return
	}
	_, err := os.Stat(path)
	if err != nil {
		fmt.Fprintf(w, "%v", http.StatusNotFound)
		return
	}

	type FileInfo struct {
		FileName     string `json:"FileName"`
		FileSize     int64  `json:"FileSize"`
		LastModified string `json:"LastModified"`
		//MD5Hash      string `json:"MD5Hash"`
	}
	type FileList struct {
		FileList []FileInfo `json:"FileList"`
	}

	var infoList []FileInfo
	err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			var fileInfo FileInfo
			fileInfo.FileName = info.Name()
			fileInfo.FileSize = info.Size()
			fileInfo.LastModified = info.ModTime().Format("2006-01-02 15:04:05")
			//fileInfo.MD5Hash = fileMD5(path)
			infoList = append(infoList, fileInfo)
		}
		return nil
	})

	if err != nil {
		fmt.Fprintf(w, "%s", err.Error())
		return
	}

	fileList := FileList{infoList}
	jsonObj, err := json.MarshalIndent(fileList, "", "    ")
	if err != nil {
		fmt.Fprintf(w, "%s", err.Error())
		return
	}
	fmt.Fprintf(w, "%s", string(jsonObj))
}

func handleSetUutInfo(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	if r.RequestURI == "/favicon.ico" {
		return
	}

	var err error
	host := ""
	if cfg.Settings.HostMode == 0 {
		host = strings.Split(r.Host, ":")[0]
	}
	if cfg.Settings.HostMode == 1 {
		host, err = os.Hostname()
		if err != nil {
			host = "hostname"
		}
	}

	addr := strings.Split(r.RemoteAddr, ":")[0]
	uri := r.RequestURI
	rst := map[string]string{"RESULT": ""}

	if r.Method != http.MethodPost {
		rst["RESULT"] = "NG"
		rst["ErrMsg"] = fmt.Sprintf("MethodNotAllowed: %v", http.StatusMethodNotAllowed)
		w.WriteHeader(http.StatusMethodNotAllowed)
		fmt.Fprintf(w, "%v", strings.ReplaceAll(fmt.Sprintf("%#v", rst), "map[string]string", ""))
		return
	}

	params, err := parseReqParams(r)
	if err != nil {
		logger.Printf("[ERR]  %s; %s; %#v; %s\r\n", r.RemoteAddr, r.RequestURI, params, rst["ErrMsg"])
		rst["RESULT"] = "NG"
		rst["ErrMsg"] = err.Error()
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintf(w, "%v", strings.ReplaceAll(fmt.Sprintf("%#v", rst), "map[string]string", ""))
		return
	}

	if cfg.Settings.LogRequest {
		logger.Printf("[MSG]  %s; %s; Request: %#v\r\n", addr, uri, params)
	}
	ews := new(ews.EWS)
	rst = ews.SetUutInfo(cfg, logger, host, addr, uri, params)
	if cfg.Settings.LogResponse {
		logger.Printf("[MSG]  %s; %s; Response: %#v\r\n", addr, uri, rst)
	}

	if rst["RESULT"] != "OK" {
		w.WriteHeader(http.StatusInternalServerError)
	} else {
		w.WriteHeader(http.StatusOK)
	}
	fmt.Fprintf(w, "%v", strings.ReplaceAll(fmt.Sprintf("%#v", rst), "map[string]string", ""))
}

func handleGetUutInfo(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	if r.RequestURI == "/favicon.ico" {
		return
	}
	host := strings.Split(r.Host, ":")[0]
	addr := strings.Split(r.RemoteAddr, ":")[0]
	uri := r.RequestURI
	rst := map[string]string{"RESULT": ""}

	if err := r.ParseForm(); err != nil {
		logger.Printf("[ERR]  %s; %s; %s\r\n", r.RemoteAddr, r.RequestURI, err.Error())
		rst["RESULT"] = "NG"
		rst["ErrMsg"] = err.Error()
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, "%v", strings.ReplaceAll(fmt.Sprintf("%#v", rst), "map[string]string", ""))
		return
	}

	params, err := parseReqParams(r)
	if err != nil {
		logger.Printf("[ERR]  %s; %s; %#v; %s\r\n", r.RemoteAddr, r.RequestURI, params, rst["ErrMsg"])
		rst["RESULT"] = "NG"
		rst["ErrMsg"] = err.Error()
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintf(w, "%v", strings.ReplaceAll(fmt.Sprintf("%#v", rst), "map[string]string", ""))
		return
	}

	if cfg.Settings.LogRequest {
		logger.Printf("[MSG]  %s; %s; Request: %#v\r\n", addr, uri, params)
	}
	ews := new(ews.EWS)

	switch params["ACTION"] {
	case "QueryByFilter":
		rst = ews.GetUutInfo(cfg, logger, host, addr, uri, params)
	case "GetIssueReport":
		rst = ews.GetIssueReport(cfg, logger, host, addr, uri, params)
	case "GetOfflineReport":
		rst = ews.GetOfflineReport(cfg, logger, host, addr, uri, params)
	default:
		rst = ews.GetUutInfo(cfg, logger, host, addr, uri, params)
	}

	if cfg.Settings.LogResponse {
		logger.Printf("[MSG]  %s; %s; Response: Length=%d\r\n", addr, uri, len(rst["ErrMsg"]))
	}

	if rst["RESULT"] != "OK" {
		w.WriteHeader(http.StatusInternalServerError)
	} else {
		w.WriteHeader(http.StatusOK)
	}
	fmt.Fprintf(w, "%v", rst["ErrMsg"])
}

func parseReqParams(r *http.Request) (map[string]string, error) {
	params := make(map[string]string, 0)
	if err := r.ParseForm(); err != nil {
		logger.Printf("[ERR]  %s; %s; %s\r\n", r.RemoteAddr, r.RequestURI, err.Error())
		return params, err
	}
	if len(r.Form) > 0 {
		for k, v := range r.Form {
			params[strings.ToUpper(strings.TrimSpace(k))] = strings.ReplaceAll(strings.TrimSpace(v[0]), "\"", "")
		}
	}
	return params, nil
}

/* func fileMD5(filePath string) string {
	file, err := os.Open(filePath)
	if err != nil {
		return ""
	}
	hash := md5.New()
	_, err = io.Copy(hash, file)
	if err != nil {
		return ""
	}
	return hex.EncodeToString(hash.Sum(nil))
} */

func chkParams(arg string) bool {
	if strings.Contains(arg, ";") ||
		strings.Contains(arg, "|") ||
		strings.Contains(arg, "&") ||
		strings.Contains(arg, ">") ||
		strings.Contains(arg, "<") ||
		strings.Contains(arg, "(") ||
		strings.Contains(arg, ")") {
		return false
	}
	return true
}

func runCommand(cmdStr string, argStr []string, requestID string) string {
	cmd := exec.Command(cmdStr, argStr...)
	if errors.Is(cmd.Err, exec.ErrDot) {
		cmd.Err = nil
	}
	std, err := cmd.StdoutPipe()
	if err != nil {
		logger.Printf("RequestID: %s; ErrMsg: %s\r\n", requestID, err.Error())
		return err.Error()
	}
	defer std.Close()
	err = cmd.Start()
	if err != nil {
		logger.Printf("RequestID: %s; ErrMsg: %s\r\n", requestID, err.Error())
		return err.Error()
	}
	ret, err := io.ReadAll(std)
	if err != nil {
		logger.Printf("RequestID: %s; ErrMsg: %s\r\n", requestID, err.Error())
		return err.Error()
	}
	err = cmd.Wait()
	if err != nil {
		logger.Printf("RequestID: %s; ErrMsg: %s\r\n", requestID, err.Error())
		return err.Error()
	}
	//fmt.Printf("\r\n%s\r\n\r\n", string(ret))
	//logger.Printf("RequestID: %s; Result: \r\n%s\r\n\r\n", requestID, string(ret))
	return string(ret)
}

func showVersion() {
	fmt.Printf("%s\r\nVersion: %s\r\n", os.Args[0], ver)
}
