package request

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"os/user"
	"path/filepath"
	"syscall"

	email "git.tilde.town/tildetown/town/email"
)

const pwLetters = "!@#$%^&*()[]{}:;,.<>/?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"

func ProcessGitea(rp string) error {
	apiToken := os.Getenv("GITEA_TOKEN")
	if apiToken == "" {
		return errors.New("need GITEA_TOKEN")
	}

	gtPath := filepath.Join(RequestPath, "gitea")

	files, err := ioutil.ReadDir(gtPath)
	if err != nil {
		return err
	}

	usernames := []string{}

	for _, file := range files {
		sysInfo := file.Sys()
		uid := sysInfo.(*syscall.Stat_t).Uid
		user, err := user.LookupId(fmt.Sprintf("%d", uid))
		if err != nil {
			fmt.Fprintf(os.Stderr, "failed to get owner of file named '%s': %s", file.Name(), err)
			continue
		}
		usernames = append(usernames, user.Username)
	}

	if len(usernames) == 0 {
		return nil
	}

	for _, username := range usernames {
		exists, err := giteaUserExists(apiToken, username)
		if err != nil {
			fmt.Fprintf(os.Stderr, "unable to check for existing account for %s: %s\n", username, err)
			continue
		}

		if !exists {
			password, err := createGiteaUser(apiToken, username)
			if err != nil {
				fmt.Fprintf(os.Stderr, "unable to create account for %s: %s\n", username, err)
				continue
			}
			err = sendGiteaEmail(username, password)
			if err != nil {
				fmt.Fprintf(os.Stderr, "failed to send email to %s; reach out manually: %s\n", username, err)
			}
		}

		os.Remove(filepath.Join(gtPath, username))
	}

	return nil
}

func createGiteaUser(apiToken, username string) (string, error) {
	client := &http.Client{}
	password := genGiteaPassword()

	// TODO using local email sucks for obvious reasons but it'll have to do for now. ideally password
	// resets can be set local to the server, so the thing to change is not the local email but the
	// ability for gitea to send mail internally.
	createPayload := struct {
		Email              string
		FullName           string `json:"full_name"`
		Login              string `json:"login_name"`
		MustChangePassword bool   `json:"must_change_password"`
		Password           string
		SendNotify         bool `json:"send_notify"`
		Username           string
		SourceId           int `json:"source_id"`
	}{
		Email:              fmt.Sprintf("%s@tilde.town", username),
		FullName:           username,
		Login:              username,
		MustChangePassword: true,
		Password:           password,
		SendNotify:         false,
		Username:           username,
		SourceId:           0,
	}

	body, err := json.Marshal(createPayload)
	if err != nil {
		return "", err
	}

	req, err := giteaAPIReq(apiToken, "POST", "admin/users", bytes.NewBuffer(body))
	if err != nil {
		return "", err
	}

	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}

	if resp.StatusCode != 201 {
		lol, _ := ioutil.ReadAll(resp.Body)
		fmt.Printf("DBG %#v\n", string(lol))
		return "", fmt.Errorf("failed to create user for %s; error code %d", username, resp.StatusCode)
	}

	return password, nil
}

func giteaUserExists(apiToken, username string) (bool, error) {
	client := &http.Client{}
	req, err := giteaAPIReq(apiToken, "GET", "users/"+username, nil)
	if err != nil {
		return false, err
	}

	resp, err := client.Do(req)
	if err != nil {
		return false, err
	}

	if resp.StatusCode == 200 {
		return true, nil
	} else if resp.StatusCode == 404 {
		return false, nil
	} else {
		return false, fmt.Errorf("unexpected response code: %d", resp.StatusCode)
	}
}

func giteaAPIReq(apiToken, method, path string, body *bytes.Buffer) (*http.Request, error) {
	if body == nil {
		body = bytes.NewBufferString("")
	}
	basePath := "https://git.tilde.town/api/v1/"
	req, err := http.NewRequest(method, basePath+path, body)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Authorization", fmt.Sprintf("token %s", apiToken))
	req.Header.Add("Content-Type", "application/json")

	return req, nil
}

func sendGiteaEmail(username, password string) error {
	body := fmt.Sprintf(`hi %s!

you requested a git.tilde.town account and now you have one~

please log in with username %s and password %s

you'll be prompted to change the password.

if you did _not_ request this, please let an admin know.
`, username, username, password)

	return email.SendLocalEmail(username, "gitea", body)
}

func genGiteaPassword() string {
	b := make([]byte, 20)
	for i := range b {
		b[i] = pwLetters[rand.Intn(len(pwLetters))]
	}
	// Bootleg, but hack to ensure we meet complexity requirement
	return string(b) + "!" + "1" + "A"
}
