package pages

import (
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"sort"
	"strconv"
	"strings"

	"github.com/ProjectSegfault/publapi/utils"
	"github.com/gofiber/fiber/v2"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/viper"
)

type Userstruct struct {
	Status int        `json:"status"`
	Online int        `json:"online"`
	Total  int        `json:"total"`
	Users  []Userinfo `json:"users"`
}

type Userinfo struct {
	Name      string `json:"name"`
	FullName  string `json:"fullName"`
	Desc      string `json:"desc"`
	Online    bool   `json:"online"`
	Op        bool   `json:"op"`
	Created   int    `json:"created"`
	Email     string `json:"email"`
	Matrix    string `json:"matrix"`
	Fediverse string `json:"fediverse"`
	Website   string `json:"website"`
	Capsule   string `json:"capsule"`
	Loc       string `json:"loc"`
}

type ByAdminAndName []Userinfo

func (a ByAdminAndName) Len() int { return len(a) }
func (a ByAdminAndName) Less(i, j int) bool {
	// If one person is an admin and the other is not, place the admin first
	if a[i].Op && !a[j].Op {
		return true
	} else if !a[i].Op && a[j].Op {
		return false
	}

	// If both persons are admins or both are not, sort by name
	return a[i].Name < a[j].Name
}
func (a ByAdminAndName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

func userdata(username, usersonline, ops string) Userinfo {
	regex := "(^| )" + username + "($| )"
	isonline, err := regexp.MatchString(string(regex), string(usersonline))
	if err != nil {
		log.Error(err)
	}
	isop, operr := regexp.MatchString(string(regex), string(ops))
	if operr != nil {
		log.Error(err)
	}
	cmd := "/usr/bin/stat -c %W /home/" + username
	crd, crerr := exec.Command("bash", "-c", cmd).Output()
	if crerr != nil {
		log.Error(crerr)
	}
	crdstr := string(crd)
	crdstr = strings.TrimSuffix(crdstr, "\n")
	filename := "/home/" + username + "/meta-info.toml"
	_, error := os.Stat(filename)
	if error != nil {
		if os.IsNotExist(error) {
			log.Error(username + " does not have a meta-info.toml")
			var user Userinfo
			user.Name = username
			user.Created, _ = strconv.Atoi(crdstr)
			if isonline == true {
				user.Online = true
			} else {
				user.Online = false
			}
			if isop == true {
				user.Op = true
			} else {
				user.Op = false
			}
			return user
		}
	}
	viper.SetConfigFile(filename)
	viper.ReadInConfig()
	var user Userinfo
	user = Userinfo{}
	user.Name = username
	user.Created, _ = strconv.Atoi(crdstr)
	user.FullName = viper.GetString("fullname")
	user.Capsule = viper.GetString("gemini")
	user.Website = viper.GetString("website")
	user.Desc = viper.GetString("description")
	user.Email = viper.GetString("email")
	user.Matrix = viper.GetString("matrix")
	user.Fediverse = viper.GetString("fediverse")
	user.Loc = viper.GetString("location")
	if isop == true {
		user.Op = true
	} else {
		user.Op = false
	}
	if isonline == true {
		user.Online = true
	} else {
		user.Online = false
	}
	return user
}

func UsersPage(c *fiber.Ctx) error {
	if runtime.GOOS == "windows" {
		return c.JSON(fiber.Map{
			"message": "/users is not supported on Windows",
			"status":  c.Response().StatusCode(),
		})
	}
	// Get the number of users online
	usersonline, err := exec.Command("bash", "-c", "/usr/bin/users").Output()
	if err != nil {
		log.Error(err)
		return c.SendStatus(fiber.StatusInternalServerError)
	}
	usersonlinestr := string(usersonline)
	usersonlinededup := utils.Dedup(usersonlinestr)
	var output int
	if strings.Contains(usersonlinededup, " ") {
		outputa := int(strings.Count(usersonlinededup, " "))
		output = outputa + 1
	} else if usersonlinededup == "" {
		output = 0
	} else {
		output = 1
	}
	// Get OPs
	ops, opserr := exec.Command("bash", "-c", "/usr/bin/members sudo").Output()
	if opserr != nil {
		log.Error(err)
		return c.SendStatus(fiber.StatusInternalServerError)
	}
	opstr := string(ops)
	// Get all users
	users, err2 := exec.Command("bash", "-c", "/usr/bin/ls /home").Output()
	if err2 != nil {
		log.Error(err2)
		return c.SendStatus(fiber.StatusInternalServerError)
	}
	userstr := string(users)
	userstr2 := strings.TrimSuffix(userstr, "\n")
	usersarr := strings.Split(userstr2, "\n")
	// Fill user info
	var userinfostruct []Userinfo
	for i := 0; i < len(usersarr); i++ {
		uname := string(usersarr[i])
		userinfostruct = append(
			userinfostruct,
			userdata(
				uname,
				strings.TrimSuffix(usersonlinededup, "\n"),
				strings.TrimSuffix(opstr, "\n"),
			),
		)
	}
	sort.Sort(ByAdminAndName(userinfostruct))
	data := Userstruct{
		Status: c.Response().StatusCode(),
		Online: output,
		Users:  userinfostruct,
		Total:  int(strings.Count(userstr, "\n")),
	}
	return c.JSON(data)
}
