package main

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"

	"git.arozos.com/ArSamba/apt"

	"git.arozos.com/ArSamba/aroz"
)

var (
	handler *aroz.ArozHandler
)

func SetupCloseHandler() {
	c := make(chan os.Signal, 2)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		<-c
		log.Println("\r- Shutting down ArSamba module.")

		os.Exit(0)
	}()
}

func main() {
	//If you have other flags, please add them here

	//Start the aoModule pipeline (which will parse the flags as well). Pass in the module launch information
	handler = aroz.HandleFlagParse(aroz.ServiceInfo{
		Name:        "ArSamba",
		Desc:        "arozos Samba Setting Subservice",
		Group:       "System Settings",
		IconPath:    "arsamba/img/icon.png",
		Version:     "1.0",
		StartDir:    "arsamba/index.html",
		SupportFW:   true,
		LaunchFWDir: "arsamba/index.html",
		InitFWSize:  []int{350, 560},
	})

	//Register the standard web services urls
	fs := http.FileServer(http.Dir("./web"))
	http.HandleFunc("/create", handleNewUser)
	http.HandleFunc("/remove", handleUserRemove)
	http.HandleFunc("/getStatus", handleGetStatus)
	http.Handle("/", fs)

	SetupCloseHandler()

	go func(port string) {
		log.Println("ArSamba subservice started. Listening on " + handler.Port)
		err := http.ListenAndServe(port, nil)
		if err != nil {
			log.Fatal(err)
		}
	}(handler.Port)

	//Mkdir for user samba profile
	os.MkdirAll("./profiles", 0755)

	//Install samba if it is not installed
	pm := apt.NewPackageManager(true)
	err := pm.InstallIfNotExists("samba", true)
	if err != nil {
		panic(err)
	}

	//Do a blocking loop
	select {}
}

func handleGetStatus(w http.ResponseWriter, r *http.Request) {
	//Get username from request
	username, _ := handler.GetUserInfoFromRequest(w, r)

	//Check if the user has already in samba user
	log.Println("Checking User Status", username)
	userExists := false
	out, err := execute("pdbedit -L | grep " + username)
	if err != nil {
		userExists = false
	}

	if strings.TrimSpace(string(out)) != "" {
		userExists = true
	}

	//Send the results
	js, _ := json.Marshal(userExists)
	sendJSONResponse(w, string(js))
}

func handleNewUser(w http.ResponseWriter, r *http.Request) {
	//Get the required information
	username, err := mv(r, "username", true)
	if err != nil {
		sendErrorResponse(w, "Invalid username given")
		return
	}

	//Match the session username
	proxyUser, token := handler.GetUserInfoFromRequest(w, r)
	if username != proxyUser {
		sendErrorResponse(w, "User not logged in")
		return
	}

	password, err := mv(r, "password", true)
	if err != nil {
		sendErrorResponse(w, "Invalid password given")
		return
	}

	//Add the user to samba
	log.Println("Adding User", username)
	//Add user to linux
	out, _ := execute("useradd -m \"" + username + "\"")
	log.Println(string(out))

	//Set password for the new user
	out, _ = execute(`(echo "` + password + `"; sleep 1; echo "` + password + `";) | passwd "` + username + `"`)
	log.Println(string(out))

	//Add it to samba user
	out, _ = execute(`(echo "` + password + `"; sleep 1; echo "` + password + `" ) | sudo smbpasswd -s -a "` + username + `"`)
	log.Println(string(out))

	//Create an AGI Call that get the user's storage directories files
	script := `
	requirelib("filelib");
	//Get the roots of this user
	var roots = filelib.glob("/");
	var userdirs = [];
	for (var i = 0; i < roots.length; i++){
		//Translate all these roots to realpath
		userdirs.push([roots[i].split(":").shift(), decodeAbsoluteVirtualPath(roots[i]), pathCanWrite(roots[i])])
	}
	
	sendJSONResp(JSON.stringify(userdirs))
	`

	userProfile := []string{}

	//Execute the AGI request on server side
	resp, err := handler.RequestGatewayInterface(token, script)
	if err != nil {
		//Something went wrong when performing POST request
		log.Println(err)
	} else {
		//Try to read the resp body
		bodyBytes, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Println(err)
			w.Write([]byte(err.Error()))
			return
		}
		resp.Body.Close()

		log.Println(string(bodyBytes))

		//Decode the json
		type Results [][]interface{}
		results := new(Results)
		err = json.Unmarshal(bodyBytes, &results)
		if err != nil {
			log.Println(err)
			return
		}

		log.Println(results)

		//Generate user root folders
		for _, r := range *results {
			if len(r) == 3 {
				pathname := r[0].(string)
				if pathname == "tmp" {
					//Do not expose tmp folder
					continue
				}
				rpath := r[1].(string)
				canWrite := r[2].(bool)
				uuidOfStorage := username + " (" + pathname + ")"
				if canWrite {
					userProfile = append(userProfile, `[`+uuidOfStorage+`]
	comment=`+username+"'s "+pathname+`
	path=`+rpath+`
	read only=no
	valid users = `+username+`
	guest ok=no
	browseable=yes
	create mask=0777
	directory mask=0777`)
				} else {
					userProfile = append(userProfile, `[`+uuidOfStorage+`]
	comment=`+username+"'s "+pathname+`
	path=`+rpath+`
	read only=yes
	valid users = `+username+`
	guest ok=no
	browseable=yes
	create mask=0777
	directory mask=0777`)
				}
			}
		}

	}

	log.Println(strings.Join(userProfile, "\n\n"))

	//Write the user profiles to file
	ioutil.WriteFile("./profiles/"+username+".conf", []byte(strings.Join(userProfile, "\n\n")), 0755)

	updateSmbConfig()
	//Return ok
	sendOK(w)
}

func handleUserRemove(w http.ResponseWriter, r *http.Request) {
	//Get the required information
	username, err := mv(r, "username", true)
	if err != nil {
		sendErrorResponse(w, "Invalid username given")
		return
	}

	//Match the session username
	proxyUser, _ := handler.GetUserInfoFromRequest(w, r)
	if username != proxyUser {
		sendErrorResponse(w, "User not logged in")
		return
	}

	//OK! Remove user
	log.Println("Remove user", username)

	//Remove user from samba
	out, _ := execute("smbpasswd -x \"" + username + "\"")
	log.Println(string(out))

	//Remove user from linux as well
	out, _ = execute("userdel -r  \"" + username + "\"")
	log.Println(string(out))

	//Remove user profiles
	if fileExists("./profiles/" + username + ".conf") {
		os.Remove("./profiles/" + username + ".conf")
	}
	updateSmbConfig()

	//Return OK
	sendOK(w)
}

func updateSmbConfig() {
	//Update the system config
	profiles, _ := filepath.Glob("./profiles/*.conf")
	base, _ := ioutil.ReadFile("smb.conf")
	additionalProfiles := []string{}
	for _, profile := range profiles {
		thisProfileContent, _ := ioutil.ReadFile(profile)
		additionalProfiles = append(additionalProfiles, string(thisProfileContent))
	}

	finalConfigFile := string(base) + strings.Join(additionalProfiles, "\n\n")

	ioutil.WriteFile("/etc/samba/smb.conf", []byte(finalConfigFile), 0777)

	out, err := execute("systemctl restart smbd.service")
	log.Println("Samba restarted: ", string(out), err)
}

func execute(command string) (string, error) {
	cmd := exec.Command("bash", "-c", command)
	out, err := cmd.CombinedOutput()
	if err != nil {
		return string(out), err
	}
	return string(out), nil
}
