// Package sso 群辉登录
package sso

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/url"
	"os"
	"os/exec"
	"regexp"
	"strings"
)

// AppPrivilege is part of AuthJSON
type AppPrivilege struct {
	IsPermitted bool `json:"SYNO.SDS.DNSCryptProxy.Application"`
}

// Session is part of AuthJSON
type Session struct {
	IsAdmin bool `json:"is_admin"`
}

// AuthJSON is used to read JSON data from /usr/syno/synoman/webman/initdata.cgi
type AuthJSON struct {
	Session      Session `json:"session"`
	AppPrivilege AppPrivilege
}

type RespJSON struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
	Data any    `json:"data"`
}

type UserData struct {
	User      string `json:"user"`
	SynoToken string `json:"synoToken"`
	Data      string `json:"data"`
}

type SynoResp struct {
	StatusCode int
	Message    string
	Data       UserData
	Ok         bool
}

func (r *SynoResp) PrintNoData() {
	fmt.Print(r.Message)
}

func OkResp() SynoResp {
	return SynoResp{
		StatusCode: 200,
		Message:    "Status: 200 OK\nContent-Type: application/json; charset=utf-8\n\n",
		Ok:         true,
	}
}

func UnauthorisedResp() SynoResp {
	return SynoResp{
		StatusCode: 401,
		Message:    "Status: 401 Unauthorized\nContent-Type: text/html; charset=utf-8\n\n",
		Ok:         false,
	}
}

func ErrorResp() SynoResp {
	return SynoResp{
		StatusCode: 500,
		Message:    "Status: 500 Internal server error\nContent-Type: text/html; charset=utf-8\n\n",
		Ok:         false,
	}
}

// Retrieve login status and try to retrieve a CSRF token.
// If either fails than we return an error to the user that they need to login.
// Returns username or error
// 现在没有权限访问这个，需要客户端进行调用
func token() (string, error) {
	cmd := exec.Command("/usr/syno/synoman/webman/login.cgi")
	cmdOut, err := cmd.Output()
	if err != nil && err.Error() != "exit status 255" { // in the Synology world, error code 255 apparently means success!
		return string(cmdOut), err
	}
	// cmdOut = bytes.TrimLeftFunc(cmdOut, findJSON)

	// Content-Type: text/html [..] { "SynoToken" : "GqHdJil0ZmlhE", "result" : "success", "success" : true }
	r, err := regexp.Compile("SynoToken\" *: *\"([^\"]+)\"")
	if err != nil {
		return string(cmdOut), err
	}
	token := r.FindSubmatch(cmdOut)
	if len(token) < 1 {
		return string(cmdOut), errors.New("Sorry, you need to login first!")
	}
	return string(token[1]), nil
}

// Detect if the rune (character) contains '{' and therefore is likely to contain JSON
// returns bool
func findJSON(r rune) bool {
	if r == '{' {
		return false
	}
	return true
}

// Check if the logged in user is Authorised or Admin.
// If either fails than we return a HTTP Unauthorized error.
func SynoAuth() SynoResp {

	//to, err := token()
	//if err != nil {
	//	log.Println("getToken fail", err)
	//	return UnauthorisedResp()
	//} else {
	//	log.Println("getToken ok...", to)
	//}

	// X-SYNO-TOKEN:9WuK4Cf50Vw7Q
	// http://192.168.1.1:5000/webman/3rdparty/DownloadStation/webUI/downloadman.cgi?SynoToken=9WuK4Cf50Vw7Q
	tempQueryEnv := os.Getenv("QUERY_STRING")
	log.Println("tempQueryEnv: ", tempQueryEnv)
	if tempQueryEnv == "" || !strings.Contains(tempQueryEnv, "SynoToken") {
		log.Println("query str miss SynoToken")
		return ErrorResp()
	}

	u, err := url.Parse(fmt.Sprintf("http://test.example.cn/?%s", tempQueryEnv))
	if err != nil {
		log.Println("parse query params fail: ", err)
		return ErrorResp()
	}
	query := u.Query()
	tokenStr := query.Get("SynoToken")

	//os.Setenv("QUERY_STRING", "SynoToken="+to)
	cmd := exec.Command("/usr/syno/synoman/webman/modules/authenticate.cgi")
	user, err := cmd.Output()
	if err != nil && string(user) == "" {
		log.Println("authenticate fail", err)
		return UnauthorisedResp()
	}
	log.Println("user: ", string(user))
	account := strings.NewReplacer("\r", "").Replace(string(user))
	account = strings.NewReplacer("\n", "").Replace(account)
	var userData = UserData{
		User:      account,
		SynoToken: tokenStr,
	}
	// check permissions
	if checkIfFileExists("/usr/syno/synoman/webman/initdata.cgi") {
		cmd = exec.Command("/usr/syno/synoman/webman/initdata.cgi") // performance hit
		cmdOut, err := cmd.Output()
		if err != nil {
			log.Println("initdata fail", err)
			//unauthorised()
		}
		cmdOut = bytes.TrimLeftFunc(cmdOut, findJSON)

		var jsonData AuthJSON
		if err := json.Unmarshal(cmdOut, &jsonData); err != nil { // performance hit
			log.Println("initdata Unmarshal fail", err)
			//unauthorised()
		}

		isAdmin := jsonData.Session.IsAdmin              // Session.IsAdmin:true
		isPermitted := jsonData.AppPrivilege.IsPermitted // AppPrivilege.SYNO.SDS.DNSCryptProxy.Application:true
		if !(isAdmin || isPermitted) {
			log.Println("initdata Unmarshal fail", err)
			//unauthorised()
		}
	}

	//os.Setenv("QUERY_STRING", tempQueryEnv)
	resp := OkResp()
	resp.Data = userData
	return resp
}

// Return true if the file path exists.
func checkIfFileExists(file string) bool {
	_, err := os.Stat(file)
	if err != nil {
		if os.IsNotExist(err) {
			return false
		}
		log.Printf("checkIfFileExists error: %v", err)
	}
	return true
}
