package actions

import (
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"maps"
	"sort"
	"strconv"
)

type Action interface {
	Name() string
	Invoke(args ActionArgs) error
}

type ActionArg interface {
	String() string
	FromString(str string) error
}

type actionArgString string // the string is base64 encoded.

func ActionArgString(s string) *actionArgString {
	base64Str := base64.StdEncoding.EncodeToString([]byte(s))
	arg := actionArgString(base64Str)
	return &arg
}

func (arg actionArgString) String() string {
	str, err := base64.StdEncoding.DecodeString(string(arg))
	if err != nil {
		panic(err)
	}
	return string(str)
}

func (arg *actionArgString) FromString(s string) error {
	base64Str := base64.StdEncoding.EncodeToString([]byte(s))
	*arg = actionArgString(base64Str)
	return nil
}

type actionArgBool bool

func ActionArgBool(b bool) *actionArgBool {
	arg := actionArgBool(b)
	return &arg
}

func (arg actionArgBool) String() string {
	return strconv.FormatBool(bool(arg))
}

func (arg *actionArgBool) FromString(s string) error {
	val, err := strconv.ParseBool(s)
	if err != nil {
		return err
	}
	*arg = actionArgBool(val)
	return nil
}

type actionArgInt int

func ActionArgInt(i int) *actionArgInt {
	arg := actionArgInt(i)
	return &arg
}

func (arg actionArgInt) String() string {
	return fmt.Sprintf("%v", int(arg))
}

func (arg *actionArgInt) FromString(s string) error {
	val, err := strconv.ParseInt(s, 10, 64)
	if err != nil {
		return err
	}
	*arg = actionArgInt(val)
	return nil
}

type ActionArgs map[string]ActionArg

func (args *ActionArgs) UnmarshalJSON(data []byte) error {
	// Temporary map to store the unmarshalled JSON data
	tempMap := make(map[string]string)

	// Unmarshal the data into the temporary map
	if err := json.Unmarshal(data, &tempMap); err != nil {
		return err
	}

	// Allocate memory for the ActionArgs map
	*args = make(ActionArgs)

	// Iterate over the unmarshalled map and process each value
	for key, value := range tempMap {
		// Check if the value can be parsed as an integer
		var actionArg ActionArg
		if intValue, err := strconv.Atoi(value); err == nil {
			v := ActionArgInt(intValue)
			actionArg = v
		} else if boolValue, err := strconv.ParseBool(value); err == nil {
			v := ActionArgBool(boolValue)
			actionArg = v
		} else {
			v := actionArgString(value)
			actionArg = &v
		}

		// Store the parsed ActionArg in the map
		(*args)[key] = actionArg
	}

	return nil
}

func (args ActionArgs) Id() string {
	if len(args) == 0 {
		return ""
	}
	var keys []string
	for key := range args {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	var id string
	for _, key := range keys {
		val := args[key]
		valStr := val.String()
		id += key + "=" + valStr + ";"
	}
	sha := sha256.New()
	_, err := sha.Write([]byte(id))
	if err != nil {
		panic(fmt.Errorf("sha256 error, should never happen. input `%v`", id))
	}
	digest := sha.Sum(nil)
	return base64.StdEncoding.EncodeToString(digest)
}

func (args ActionArgs) GetString(name string) (string, error) {
	val, ok := args[name]
	if !ok {
		return "", fmt.Errorf("ActionArgs.GetString: arg `%v` not found", name)
	}
	return val.String(), nil
}

func (args ActionArgs) GetBool(name string) (bool, error) {
	val, ok := args[name]
	if !ok {
		return false, fmt.Errorf("ActionArgs.GetBool: arg `%v` not found", name)
	}
	return strconv.ParseBool(val.String())
}

func (args ActionArgs) Clone() ActionArgs {
	clone := make(ActionArgs)
	maps.Copy(clone, args)
	return clone
}

func MergeActionArgs(a, b ActionArgs) ActionArgs {
	args := a.Clone()
	maps.Copy(args, b)
	return args
}
