package libamuse

import (
	"notabug.org/apiote/amuse/accounts"

	"bytes"
	"encoding/base32"
	"encoding/base64"
	"errors"
	"image"
	"strings"

	"github.com/chai2010/webp"
	"github.com/pquerna/otp"
	"github.com/pquerna/otp/totp"
	"notabug.org/apiote/gott"
)

func createSecret(args ...interface{}) (interface{}, error) {
	var (
		err     error
		secretB []byte
	)

	result := args[1].(*Result)
	secret := args[4].(string)
	host := args[6].(string)

	if len(secret) > 0 {
		secretB, err = base32.StdEncoding.DecodeString(secret)
	}
	opts := totp.GenerateOpts{
		Issuer:      host,
		AccountName: "nearly_headless_nick@" + host,
		Secret:      secretB,
	}
	key, err := totp.Generate(opts)
	result.result = key
	return gott.Tuple(args), err
}

func createImage(args ...interface{}) (interface{}, error) {
	result := args[1].(*Result)
	secret := result.result.(*otp.Key)
	image, err := secret.Image(256, 256)
	result.result2 = image
	return gott.Tuple(args), err
}

func encodeWebp(args ...interface{}) (interface{}, error) {
	result := args[1].(*Result)
	image := result.result2.(image.Image)
	var buf bytes.Buffer
	err := webp.Encode(&buf, image, &webp.Options{Lossless: true})

	data := "data:image/webp;base64,"
	data += base64.StdEncoding.EncodeToString(buf.Bytes())
	result.result2 = data

	return gott.Tuple(args), err
}

func renderSignup(args ...interface{}) interface{} {
	result := args[1].(*Result)
	secret := result.result.(*otp.Key)
	qr := result.result2.(string)
	sfaEnabled := args[3].(bool)
	username := args[5].(string)

	var authError error
	if args[2] != nil {
		authError = args[2].(error)
	}
	result.page = result.renderer.RenderSignup(result.languages, authError, secret, sfaEnabled, username, qr)
	return gott.Tuple(args)
}

func ShowSignup(acceptLanguages, mimetype string, err error, sfaEnabled bool, sfaSecret, username, host string) (string, error) {
	r, err := gott.
		NewResult(gott.Tuple{&RequestData{language: acceptLanguages, mimetype: mimetype}, &Result{}, err, sfaEnabled, sfaSecret, username, host}).
		Bind(parseLanguage).
		Bind(createSecret).
		Bind(createImage).
		Bind(encodeWebp).
		Bind(createRenderer).
		Map(renderSignup).
		Finish()

	if err != nil {
		return "", err
	} else {
		return r.(gott.Tuple)[1].(*Result).page, nil
	}
}

func DoSignup(username, password, passwordConfirm string, sfaEnabled bool, sfaSecret, sfa string) (string, error) {
	if password != passwordConfirm {
		return "", accounts.AuthError{
			Err: errors.New("passwords_dont_match"),
		}
	}
	if sfaEnabled {
		if sfa == "" {
			return "", accounts.AuthError{
				Err: errors.New("sfa_not_confirmed"),
			}
		}
		sfa = strings.ReplaceAll(sfa, " ", "")
		if !totp.Validate(sfa, sfaSecret) {
			return "", accounts.AuthError{
				Err: errors.New("sfa_code_not_correct"),
			}
		}
	}
	if username == "" || password == "" || sfaSecret == "" {
		return "", accounts.AuthError{
			Err: errors.New("required_info_missing"),
		}
	}

	if !sfaEnabled {
		sfaSecret = ""
	}

	return accounts.Signup(username, password, sfaSecret)
}

func renderSignedup(args ...interface{}) interface{} {
	result := args[1].(*Result)
	recoveryCodes := args[2].(string)
	codes := []string{}
	if recoveryCodes != "" {
		codes = strings.Split(recoveryCodes, ",")
	}
	result.page = result.renderer.RenderSignedup(result.languages, codes)
	return gott.Tuple(args)
}

func ShowSignedup(acceptLanguages, mimetype, recoveryCodes string) (string, error) {
	r, err := gott.
		NewResult(gott.Tuple{&RequestData{language: acceptLanguages, mimetype: mimetype}, &Result{}, recoveryCodes}).
		Bind(parseLanguage).
		Bind(createRenderer).
		Map(renderSignedup).
		Finish()

	if err != nil {
		return "", err
	} else {
		return r.(gott.Tuple)[1].(*Result).page, nil
	}
}
