package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"strings"
	"time"

	"git.tilde.town/tildetown/town/invites"
	"git.tilde.town/tildetown/town/stats"
	"git.tilde.town/tildetown/town/towndb"
	"github.com/charmbracelet/lipgloss"

	_ "embed"
)

// TODO mark on user table what signup id led to the account for forensics
// TODO add logging like the signup tool has
// TODO consider merging adduser, usermod, and createkeyfile into single createuser helper to limit sudoers list
// TODO add alerts for new users (mailing list post, irc post, etc(?))

//go:embed welcome.txt
var welcomeArt string

type newUserData struct {
	Username    string
	DisplayName string
	Email       string
	PubKey      string
}

func defaultStyle() lipgloss.Style {
	return lipgloss.NewStyle().
		Foreground(lipgloss.AdaptiveColor{
			Light: "#7D19BD",
			Dark:  "#E0B0FF",
		})
}

func _main() error {
	inviteDB, err := invites.ConnectDB()
	if err != nil {
		return err
	}

	s := defaultStyle().SetString(welcomeArt)
	fmt.Println(s)

	code, err := promptCode()
	if err != nil {
		return err
	}

	invite, err := invites.Get(inviteDB, code)
	if err != nil {
		return fmt.Errorf("could not look up invite code: %w", err)
	}

	if invite.Used {
		return errors.New("that invite code has already been used.")
	}

	s = s.SetString("thanks!! just gotta collect some information now and then your account will be ready.")

	fmt.Println(s)

	townData, err := stats.Stats()
	if err != nil {
		return err
	}
	data := &newUserData{
		Email: invite.Email,
	}

	a := &asker{
		UserData: data,
		Invite:   *invite,
		TownData: townData,
		Style:    defaultStyle(),
	}

	if err = a.Ask(); err != nil {
		return err
	}

	conf, err := confirmContinue()
	if err != nil {
		return err
	}

	if !conf {
		for !conf {
			if err = a.Ask(); err != nil {
				return err
			}
			if conf, err = confirmContinue(); err != nil {
				return err
			}
		}
	}

	s = s.SetString("town users all agree to our code of conduct: https://tilde.town/wiki/conduct.html")

	fmt.Println(s)

	coc, err := confirmCoC()
	if err != nil {
		return err
	}

	if !coc {
		s = s.SetString("bummer. have a good one.")
		fmt.Println(s)
		return nil
	}

	s = s.SetString("cool, awesome, thank you, going to make your account now...")
	fmt.Println(s)

	if err = createUser(*data); err != nil {
		s = s.SetString(fmt.Sprintf(`augh, I'm sorry. the account creation failed.
Please email root@tilde.town and paste the following error:

%s

Your invite code has not been marked as used and you're welcome to try again,
though if there is a system issue you might need to wait for word from an admin.`, err.Error()))
		fmt.Println(s)
		return nil
	}

	s = s.SetString(fmt.Sprintf(`OK! your user account has been created.

welcome, ~%[1]s <3

This program is going to exit and you are now free to ssh to town as yourself:

  ssh %[1]s@tilde.town

if your public key isn't found by ssh, you'll need to explain to ssh how to find it with:

  ssh -i "replace with path to private key file" %[1]s@tilde.town

for help with ssh, see: https://tilde.town/wiki/getting-started/ssh.html

if you end up very stuck, you can email root@tilde.town .`, data.Username))

	fmt.Println(s)

	if err = invite.MarkUsed(inviteDB); err != nil {
		return fmt.Errorf("could not mark invite as used: %w", err)
	}

	return nil
}

func createUser(data newUserData) (err error) {
	cmd := exec.Command("sudo", "/usr/sbin/adduser", "--quiet", "--disabled-password", data.Username)
	if err = cmd.Run(); err != nil {
		return fmt.Errorf("adduser failed: %w", err)
	}

	cmd = exec.Command("sudo", "/usr/sbin/usermod", "-a", "-G", "town", data.Username)
	if err = cmd.Run(); err != nil {
		return fmt.Errorf("usermod failed: %w", err)
	}

	cmd = exec.Command("sudo", "--user", data.Username, "/town/bin/createkeyfile", data.Username)
	cmd.Stdin = bytes.NewBufferString(keyfileText(data))
	stdoutBuff := bytes.NewBuffer([]byte{})
	cmd.Stdout = stdoutBuff
	if err = cmd.Run(); err != nil {
		return fmt.Errorf("createkeyfile failed with '%s': %w", string(stdoutBuff.Bytes()), err)
	}

	cmd = exec.Command("sudo", "/town/bin/generate_welcome_present.sh", data.Username)
	if err = cmd.Run(); err != nil {
		// TODO log this. no reason to bail out.
	}

	tu := towndb.TownUser{
		Username: data.Username,
		Created:  time.Now(),
		Emails: []string{
			data.Email,
		},
		State: towndb.StateActive,
	}
	var out []byte
	if out, err = json.Marshal(tu); err != nil {
		return fmt.Errorf("could not serialize user data: %w", err)
	}
	cmd = exec.Command("sudo", "/town/bin/registeruser")
	stderrBuff := bytes.NewBuffer([]byte{})
	cmd.Stderr = stderrBuff
	cmd.Stdin = bytes.NewBuffer(out)
	if err = cmd.Run(); err != nil {
		return fmt.Errorf("register user failed with '%s': %w", string(stderrBuff.Bytes()), err)
	}

	return nil
}

func keyfileText(data newUserData) string {
	pkey := data.PubKey
	if !strings.HasSuffix(pkey, "\n") {
		pkey += "\n"
	}
	header := `########## GREETINGS! ##########
# This file was automatically generated by tilde.town when
# your account was created. You can edit it if you want, but we
# recommend adding stuff to ~/.ssh/authorized_keys instead.`

	return fmt.Sprintf("%s\n%s", header, pkey)
}

func main() {
	err := _main()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
