package server

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"net/http"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"
	"time"

	"repositories.action2quare.com/ayo/gocommon/logger"
	"repositories.action2quare.com/ayo/houston/shared"
)

/*
*
현재 접속 중인 Agent 목록을 보여줍니다.
- http method : GET
*/
const (
	sub_folder_name_deploys   = string("_deploys")
	sub_folder_name_downloads = string("_downloads")
)

func (h *houstonHandler) GetAgents(w http.ResponseWriter, r *http.Request) {
	enc := json.NewEncoder(w)
	allHosts := h.Operation().Hosts()
	enc.Encode(allHosts)
}

func readTagsFromFile(paths ...string) string {
	raw, _ := os.ReadFile(path.Join(paths...))
	if len(raw) > 0 {
		tag := string(raw)
		return strings.Trim(tag, "\n")
	}

	return ""
}

func (h *houstonHandler) GetDeploySources(w http.ResponseWriter, r *http.Request) {
	files, err := os.ReadDir(h.deployPath)
	if err != nil {
		logger.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	getVersions := func(name string) []string {
		vers, _ := os.ReadDir(path.Join(h.deployPath, name))
		mytags := readTagsFromFile(h.deployPath, name, "@tags")
		out := []string{
			mytags,
		}
		for _, fd := range vers {
			if fd.IsDir() {
				ver := fd.Name()
				files, _ := os.ReadDir(path.Join(h.deployPath, name, ver))
				vertags := readTagsFromFile(h.deployPath, name, ver, "@tags")
				if len(files) > 0 {
					for _, file := range files {
						if strings.HasPrefix(file.Name(), "@") {
							continue
						}

						downloadpath := path.Join(sub_folder_name_deploys, name, ver, file.Name())
						ver = fmt.Sprintf("%s:%s", ver+mytags+vertags, downloadpath)
						break
					}
				}
				out = append(out, ver)
			}
		}

		return out
	}

	out := make(map[string][]string)
	for _, fd := range files {
		if fd.IsDir() {
			out[fd.Name()] = getVersions(fd.Name())
		}
	}

	enc := json.NewEncoder(w)
	enc.Encode(out)
}

func (h *houstonHandler) UploadDeploySource(w http.ResponseWriter, r *http.Request) {
	// 	<form action="/houston" method="post" enctype="multipart/form-data">
	//     <input type="file" name="file">
	//     <input type="text" name="name">
	//     <input type="text" name="version">
	//     <input type="submit" value="업로드">
	//   </form>
	file, header, err := r.FormFile("file")
	if err != nil {
		logger.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	defer file.Close()

	contents, err := io.ReadAll(file)
	if err != nil {
		logger.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	version := r.FormValue("version")
	name := r.FormValue("name")
	ext := path.Ext(header.Filename)

	var filename string

	if version == "config" {
		filename = path.Join(h.deployPath, name, version, "config"+ext)
		tags := readTagsFromFile(h.deployPath, name, version, "@tags")
		if !strings.Contains(tags, "#hidden") {
			tags = tags + "#hidden"
			os.WriteFile(path.Join(h.deployPath, name, version, "@tags"), []byte(tags), 0644)
		}
	} else {
		filename = path.Join(h.deployPath, name, version, name+ext)
	}

	if err = os.MkdirAll(path.Dir(filename), 0775); err != nil {
		logger.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	// 파일 저장
	err = os.WriteFile(filename, contents, 0644)
	if err != nil {
		logger.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
}

func (h *houstonHandler) DeleteDeploySource(w http.ResponseWriter, r *http.Request) {
	// 	<form action="/houston" method="post" enctype="multipart/form-data">
	//     <input type="text" name="name">
	//     <input type="text" name="version">
	//   </form>
	version := r.FormValue("version")
	name := r.FormValue("name")

	if len(version) == 0 || len(name) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// deploys 폴더는 파일시스템 서비스이므로 다운로드 가능
	targetpath := path.Join(h.deployPath, name, version)
	if err := os.RemoveAll(targetpath); err != nil {
		logger.Println("deleteDeploySource failed :", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
}

func (h *houstonHandler) findLastestConfigFile(name string) (string, error) {
	logger.Println("findLastestConfigFile :", name)
	configFiles, err := os.ReadDir(path.Join(h.deployPath, name, "config"))
	if err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			return "", nil
		}

		logger.Println("findLastestConfigFile failed :", err)
		return "", err
	}

	var cf fs.FileInfo
	for _, file := range configFiles {
		if file.IsDir() {
			continue
		}
		if strings.HasPrefix(file.Name(), "config.") {
			test, err := file.Info()
			if err != nil {
				return "", err
			}

			if cf == nil {
				cf = test
			} else if test.ModTime().After(cf.ModTime()) {
				cf = test
			}
		}
	}

	if cf != nil {
		logger.Println("findLastestConfigFile cf found :", cf.Name())
		return path.Join(sub_folder_name_deploys, name, "config", cf.Name()), nil
	}

	logger.Println("findLastestConfigFile cf NOT found")

	return "", nil
}

func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
	// 	<form action="/houston" method="post" enctype="multipart/form-data">
	//     <input type="text" name="name">
	//     <input type="text" name="version">
	//     <input type="text" name="targets">
	//     <input type="submit" value="업로드">
	//   </form>
	name := r.FormValue("name")
	version := r.FormValue("version")
	traws := r.FormValue("targets")

	var targets []string
	if len(traws) > 0 {
		if err := json.Unmarshal([]byte(traws), &targets); err != nil {
			logger.Println(err)
			w.WriteHeader(http.StatusBadRequest)
			return
		}
	}

	if len(name) == 0 || len(version) == 0 || len(targets) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	relPath := path.Join(h.deployPath, name, version)
	files, err := os.ReadDir(relPath)
	if err != nil {
		logger.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	latestTime := time.Time{}
	var latestFilename string
	for _, fd := range files {
		if fd.IsDir() {
			continue
		}

		if strings.HasPrefix(fd.Name(), "@") {
			continue
		}

		fi, _ := fd.Info()
		if fi.ModTime().After(latestTime) {
			latestFilename = fi.Name()
			latestTime = fi.ModTime()
		}
	}
	if len(latestFilename) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	configPath, err := h.findLastestConfigFile(name)
	if err != nil {
		logger.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	h.Operation().Deploy(MakeDeployRequest(
		shared.DeployRequest{
			Name:    name,
			Version: version,
			Url:     path.Join(sub_folder_name_deploys, name, version, latestFilename),
			Config:  configPath,
		},
		targets,
	))
}

func (h *houstonHandler) Undeploy(w http.ResponseWriter, r *http.Request) {
	// 	<form action="/houston" method="post" enctype="multipart/form-data">
	//     <input type="text" name="name">
	//     <input type="text" name="version">
	//     <input type="text" name="targets">
	//   </form>
	name := r.FormValue("name")
	version := r.FormValue("version")
	traws := r.FormValue("targets")

	var targets []string
	if len(traws) > 0 {
		if err := json.Unmarshal([]byte(traws), &targets); err != nil {
			logger.Println(err)
			w.WriteHeader(http.StatusBadRequest)
			return
		}
	}

	if len(name) == 0 || len(version) == 0 || len(targets) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	h.Operation().Withdraw(MakeWithdrawRequest(
		shared.WithdrawRequest{
			Name:    name,
			Version: version,
		},
		targets,
	))
}

func (h *houstonHandler) StartProcess(w http.ResponseWriter, r *http.Request) {
	// 	<form action="/houston" method="post" enctype="multipart/form-data">
	//     <input type="text" name="name">
	//     <input type="text" name="version">
	// 		<input type="text" name="args">
	//     <input type="text" name="targets">
	//     <input type="submit" value="업로드">
	//   </form>
	name := r.FormValue("name")
	version := r.FormValue("version")
	argsline := r.FormValue("args")
	traws := r.FormValue("targets")

	var targets []string
	if len(traws) > 0 {
		if err := json.Unmarshal([]byte(traws), &targets); err != nil {
			logger.Println(err)
			w.WriteHeader(http.StatusBadRequest)
			return
		}
	}

	if len(name) == 0 || len(version) == 0 || len(targets) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	re := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`)
	argsTemp := re.FindAllString(argsline, -1)
	var args []string
	for _, arg := range argsTemp {
		if strings.HasPrefix(arg, `"`) && len(args) > 0 {
			lastarg := args[len(args)-1]
			if strings.HasSuffix(lastarg, "=") {
				args[len(args)-1] = lastarg + arg
			} else {
				args = append(args, arg)
			}
		} else {
			args = append(args, arg)
		}
	}

	if len(args) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	h.Operation().StartProcess(MakeStartProcessRequest(shared.StartProcessRequest{
		Name:    name,
		Version: version,
		Args:    args, // 실행 파일 포함 e.g. "biglocal.exe -port=8090 -dev",
	}, targets))
}

func (h *houstonHandler) StopProcess(w http.ResponseWriter, r *http.Request) {
	// 	<form action="/houston" method="post" enctype="multipart/form-data">
	//     <input type="text" name="name">
	//     <input type="text" name="version">
	// 		<input type="text" name="args">
	//     <input type="text" name="targets">
	//     <input type="submit" value="업로드">
	//   </form>
	name := r.FormValue("name")
	if len(name) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	version := r.FormValue("version") // option
	pidstr := r.FormValue("pid")      // option
	traws := r.FormValue("targets")

	var targets []string
	if len(traws) > 0 {
		if err := json.Unmarshal([]byte(traws), &targets); err != nil {
			logger.Println(err)
			w.WriteHeader(http.StatusBadRequest)
			return
		}
	}

	pid, _ := strconv.Atoi(pidstr)

	h.Operation().StopProcess(MakeStopProcessRequest(shared.StopProcessRequest{
		Name:    name,
		Version: version,
		Pid:     int32(pid),
	}, targets))
}

func (h *houstonHandler) RestartProcess(w http.ResponseWriter, r *http.Request) {
	// 	<form action="/houston" method="post" enctype="multipart/form-data">
	//     <input type="text" name="name">
	//     <input type="text" name="target">
	// 		<input type="text" name="pid">
	// 		<input type="text" name="config">
	//   </form>
	pidstr := r.FormValue("pid")
	target := r.FormValue("target")
	name := r.FormValue("name")
	if len(target) == 0 || len(pidstr) == 0 || len(name) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	deployConfig := false
	configstr := r.FormValue("config")
	if len(configstr) > 0 {
		deployConfig, _ = strconv.ParseBool(configstr)
	}

	pid, err := strconv.ParseInt(pidstr, 10, 0)
	if err != nil {
		logger.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	var configPath string
	if deployConfig {
		configPath, err = h.findLastestConfigFile(name)
		if err != nil {
			logger.Println(err)
			w.WriteHeader(http.StatusBadRequest)
			return
		}
	}

	h.Operation().RestartProcess(MakeRestartRequest(shared.RestartProcessRequest{
		Name:   name,
		Pid:    int32(pid),
		Config: configPath,
	}, []string{target}))
}

func (h *houstonHandler) UploadLogs(w http.ResponseWriter, r *http.Request) {
	// 	<form action="/houston" method="post" enctype="multipart/form-data">
	//     <input type="text" name="name">
	//     <input type="text" name="version">
	//		<input type="text" name="filter">
	//     <input type="text" name="targets">
	//     <input type="submit" value="업로드">
	//   </form>
	name := r.FormValue("name")
	if len(name) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	version := r.FormValue("version") // option
	traws := r.FormValue("targets")
	filter := r.FormValue("filter")

	if len(filter) == 0 {
		filter = "logs/*"
	}
	var targets []string
	if err := json.Unmarshal([]byte(traws), &targets); err != nil {
		logger.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	h.Operation().Upload(MakeUploadRequest(shared.UploadRequest{
		Name:    name,
		Version: version,
		Url:     "upload",
		Filter:  filter,
	}, targets))
}

func (h *houstonHandler) GetLogFileLinks(w http.ResponseWriter, r *http.Request) {
	// 	<form action="/houston" method="post" enctype="multipart/form-data">
	//     <input type="text" name="name">
	//     <input type="text" name="version">
	//   </form>
	name := r.FormValue("name")
	version := r.FormValue("version")
	if len(name) == 0 || len(version) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	if version == "latest" {
		version, _ = shared.FindLastestVersion(h.downloadPath, name)
	}

	root := path.Join(h.downloadPath, name, version)
	logfiles, err := os.ReadDir(root)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	var out []string
	for _, lf := range logfiles {
		out = append(out, path.Join(sub_folder_name_downloads, name, version, lf.Name()))
	}

	enc := json.NewEncoder(w)
	enc.Encode(out)
}

func (h *houstonHandler) GetDeployingProgress(w http.ResponseWriter, r *http.Request) {
	json.NewEncoder(w).Encode(h.Operation().DeplyingProgress())
}
