package workingdir

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"path"
	"regexp"
	"strings"

	"github.com/tg123/sshpiper/libplugin"
	"golang.org/x/crypto/ssh"

	log "github.com/sirupsen/logrus"
)

type Workingdir struct {
	Path        string
	NoCheckPerm bool
	Strict      bool
}

var (
	usernameRule *regexp.Regexp
)

const (
	userAuthorizedKeysFile = "authorized_keys"
	userKeyFile            = "id_rsa"
	userUpstreamFile       = "sshpiper_upstream"
	userKnownHosts         = "known_hosts"
)

func init() {
	// Base username validation on Debians default: https://sources.debian.net/src/adduser/3.113%2Bnmu3/adduser.conf/#L85
	// -> NAME_REGEX="^[a-z][-a-z0-9_]*\$"
	// The length is limited to 32 characters. See man 8 useradd: https://linux.die.net/man/8/useradd
	usernameRule, _ = regexp.Compile("^[a-z_][-a-z0-9_]{0,31}$")
}

func IsUsernameSecure(user string) bool {
	return usernameRule.MatchString(user)
}

func (w *Workingdir) Mapkey(pub []byte) ([]byte, error) {

	var rest []byte
	rest, err := w.Readfile(userAuthorizedKeysFile)
	if err != nil {
		return nil, err
	}

	var authedPubkey ssh.PublicKey

	for len(rest) > 0 {
		authedPubkey, _, _, rest, err = ssh.ParseAuthorizedKey(rest)

		if err != nil {
			return nil, err
		}

		if bytes.Equal(authedPubkey.Marshal(), pub) {
			log.Infof("found mapping key %v", w.fullpath(userKeyFile))
			return w.Readfile(userKeyFile)
		}
	}

	return nil, fmt.Errorf("no matching key found")
}

func (w *Workingdir) CreateUpstream() (*libplugin.Upstream, error) {

	data, err := w.Readfile(userUpstreamFile)
	if err != nil {
		return nil, err
	}

	host, port, user, err := parseUpstreamFile(string(data))
	if err != nil {
		return nil, err
	}

	return &libplugin.Upstream{
		Host:          host,
		Port:          int32(port),
		UserName:      user,
		IgnoreHostKey: !w.Strict,
	}, nil
}

func (w *Workingdir) VerifyHostKey(hostname, netaddr string, key []byte) error {
	if !w.Strict {
		return nil
	}

	f, err := os.Open(w.fullpath(userKnownHosts))
	if err != nil {
		return err
	}
	defer f.Close()

	return libplugin.VerifyHostKeyFromKnownHosts(f, hostname, netaddr, key)
}

func (w *Workingdir) checkPerm(file string) error {
	filename := path.Join(w.Path, file)
	f, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	fi, err := f.Stat()
	if err != nil {
		return err
	}

	if w.NoCheckPerm {
		return nil
	}

	if fi.Mode().Perm()&0077 != 0 {
		return fmt.Errorf("%v's perm is too open", filename)
	}

	return nil
}

func (w *Workingdir) fullpath(file string) string {
	return path.Join(w.Path, file)
}

func (w *Workingdir) Readfile(file string) ([]byte, error) {
	if err := w.checkPerm(file); err != nil {
		return nil, err
	}

	return os.ReadFile(w.fullpath(file))
}

func parseUpstreamFile(data string) (host string, port int, user string, err error) {
	r := bufio.NewReader(strings.NewReader(data))
	for {
		host, err = r.ReadString('\n')
		if err != nil {
			break
		}

		host = strings.TrimSpace(host)

		if host != "" && host[0] != '#' {
			break
		}
	}

	t := strings.SplitN(host, "@", 2)

	if len(t) > 1 {
		user = t[0]
		host = t[1]
	}

	host, port, err = libplugin.SplitHostPortForSSH(host)
	return
}
