package core

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"hash"

	"github.com/dop251/goja"
)

func toBytes(v interface{}) []byte {
	switch v := v.(type) {
	case []byte:
		return v
	case string:
		return []byte(v)
	case *Buffer:
		return v.value
	}
	return nil
}

func toString(v interface{}) string {
	switch v := v.(type) {
	case []byte:
		return string(v)
	case string:
		return v
	case *Buffer:
		return string(v.value)
	}
	return ""
}

type Crypto struct {
	vm *goja.Runtime
}

type Hmac struct {
	// decode    bool
	vm        *goja.Runtime
	Algorithm string
	middle    interface{}
}

func (h *Hmac) Digest(EF string) interface{} {
	var result interface{}
	var SF = ""
	switch h.Algorithm {
	case "md5", "sha1", "sha256", "sha512":
		result = h.middle.(hash.Hash).Sum(nil)
	case "aes-256-cbc":
	}
	if EF == "" {
		return &Buffer{
			value: result.([]byte),
		}
	}
	return Convert(h.vm, result, SF, EF)
}

func (h *Hmac) Update(data interface{}, SF, EF string) interface{} {
	switch h.Algorithm {
	case "md5", "sha1", "sha256", "sha512":
		h.middle.(hash.Hash).Write(toBytes(data))
	case "aes-256-cbc":
		if SF == "hex" {
			data = Convert(h.vm, data, "hex", "")
		}
		return Convert(h.vm, h.middle.(*AesCipher).Update(toBytes(data)), "", EF)
	}
	return nil
}

func (h *Hmac) Final(EF string) interface{} {
	switch h.Algorithm {
	case "md5", "sha1", "sha256", "sha512":
	case "aes-256-cbc":
		return Convert(h.vm, h.middle.(*AesCipher).Final(), "", EF)
	}
	return nil
}

func (c *Crypto) CreateCipheriv(algorithm string, key, iv interface{}) *Hmac {
	var middle interface{}
	var err error
	switch algorithm {
	case "aes-256-cbc":

		middle, err = NewAesCipher(c.vm, toBytes(key), toBytes(iv), false)
		if err != nil {
			panic(Error(c.vm, err))
		}
	}
	return &Hmac{
		vm: c.vm,
		// decode:    true,
		middle:    middle,
		Algorithm: algorithm,
	}
}

func (c *Crypto) CreateDecipheriv(algorithm string, key, iv interface{}) *Hmac {
	var middle interface{}
	var err error
	switch algorithm {
	case "aes-256-cbc":
		middle, err = NewAesCipher(c.vm, toBytes(key), toBytes(iv), true)
		if err != nil {
			panic(Error(c.vm, err))
		}
	}
	return &Hmac{
		// decode:    true,
		vm:        c.vm,
		middle:    middle,
		Algorithm: algorithm,
	}
}

func (c *Crypto) CreateHash(algorithm string) *Hmac {
	var hashFunc func() hash.Hash
	switch algorithm {
	case "md5":
		hashFunc = md5.New
	case "sha1":
		hashFunc = sha1.New
	case "sha256":
		hashFunc = sha256.New
	case "sha512":
		hashFunc = sha512.New
	default:
		return nil
	}
	return &Hmac{
		vm:        c.vm,
		middle:    hashFunc(),
		Algorithm: algorithm,
	}
}

func (c *Crypto) CreateHmac(algorithm string, key interface{}) *Hmac {
	var hashFunc func() hash.Hash
	switch algorithm {
	case "md5":
		hashFunc = md5.New
	case "sha1":
		hashFunc = sha1.New
	case "sha256":
		hashFunc = sha256.New
	case "sha512":
		hashFunc = sha512.New
	default:
		return nil
	}
	return &Hmac{
		vm:        c.vm,
		middle:    hmac.New(hashFunc, toBytes(key)),
		Algorithm: algorithm,
	}
}

type AesCipher struct {
	block  cipher.Block
	iv     []byte
	mode   cipher.BlockMode
	decode bool
	buffer bytes.Buffer
	vm     *goja.Runtime
}

func NewAesCipher(vm *goja.Runtime, key, iv []byte, decode bool) (*AesCipher, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	mode := cipher.NewCBCDecrypter(block, iv)
	if !decode {
		mode = cipher.NewCBCEncrypter(block, iv)
	}
	return &AesCipher{
		vm:     vm,
		block:  block,
		iv:     iv,
		mode:   mode,
		decode: decode,
	}, nil
}

func (ac *AesCipher) Update(text []byte) []byte {
	//fmt.Println("Update", string(text))
	if ac.decode {
		// Decrypt
		var err error
		//fmt.Println("Decrypt1", string(text))
		ac.mode.CryptBlocks(text, text)
		//fmt.Println("Decrypt2", string(text))
		text, err = pkcs7Unpadding(text)
		if err != nil {
			panic(Error(ac.vm, err))
		}
		//fmt.Println("Decrypt3", string(text), err)
	} else {
		// Encrypt
		text = pkcs7Padding(text, ac.block.BlockSize())
		ac.mode.CryptBlocks(text, text)
	}
	ac.buffer.Write(text)
	return nil
	// return text
}

func (ac *AesCipher) Final() []byte {
	return ac.buffer.Bytes()
}

func pkcs7Unpadding(data []byte) ([]byte, error) {
	length := len(data)
	unpadding := int(data[length-1])
	if unpadding > length {
		return nil, fmt.Errorf("pkcs7: invalid padding")
	}
	return data[:(length - unpadding)], nil
}

// type Cipher struct{}

// func (c *Cipher) AesEncrypt(key, data string) (string, error) {
// 	block, err := aes.NewCipher([]byte(key))
// 	if err != nil {
// 		return "", err
// 	}
// 	ciphertext := make([]byte, aes.BlockSize+len(data))
// 	iv := ciphertext[:aes.BlockSize]
// 	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
// 		return "", err
// 	}
// 	stream := cipher.NewCFBEncrypter(block, iv)
// 	stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(data))
// 	return base64.StdEncoding.EncodeToString(ciphertext), nil
// }

// func (c *Cipher) AesDecrypt(key, ciphertext string) (string, error) {
// 	block, err := aes.NewCipher([]byte(key))
// 	if err != nil {
// 		return "", err
// 	}
// 	data, err := base64.StdEncoding.DecodeString(ciphertext)
// 	if err != nil {
// 		return "", err
// 	}
// 	if len(data) < aes.BlockSize {
// 		return "", fmt.Errorf("ciphertext too short")
// 	}
// 	iv := data[:aes.BlockSize]
// 	data = data[aes.BlockSize:]
// 	stream := cipher.NewCFBDecrypter(block, iv)
// 	stream.XORKeyStream(data, data)
// 	return string(data), nil
// }

// func (c *Cipher) Rc4Encrypt(key, data string) (string, error) {
// 	cipher, err := rc4.NewCipher([]byte(key))
// 	if err != nil {
// 		return "", err
// 	}
// 	ciphertext := make([]byte, len(data))
// 	cipher.XORKeyStream(ciphertext, []byte(data))
// 	return base64.StdEncoding.EncodeToString(ciphertext), nil
// }

//	func (c *Cipher) Rc4Decrypt(key, ciphertext string) (string, error) {
//		cipher, err := rc4.NewCipher([]byte(key))
//		if err != nil {
//			return "", err
//		}
//		data, err := base64.StdEncoding.DecodeString(ciphertext)
//		if err != nil {
//			return "", err
//		}
//		cipher.XORKeyStream(data, data)
//		return string(data), nil
//	}

func Convert(vm *goja.Runtime, data interface{}, fromFormat string, toFormat string) interface{} {
	//fmt.Println(data, fromFormat, toFormat)
	var dataBytes []byte

	switch input := data.(type) {
	case string:
		dataBytes = []byte(input)
	case []byte:
		dataBytes = input
	case *Buffer:
		dataBytes = input.value
	default:
		panic(Error(vm, fmt.Errorf("invalid data type: %T", data)))
	}

	switch fromFormat {
	case "hex":
		// 将数据从 HEX 编码格式转换为字节数组
		decodedData, err := hex.DecodeString(string(dataBytes))
		if err != nil {
			panic(Error(vm, err))
		}
		dataBytes = decodedData
	case "base64":
		// 将数据从 Base64 编码格式转换为字节数组
		decodedData, err := base64.StdEncoding.DecodeString(string(dataBytes))
		if err != nil {
			panic(Error(vm, err))
		}
		dataBytes = decodedData
	case "bytes", "binary":
		// 不需要进行转换
	case "utf8", "utf-8", "":

	default:
		panic(Error(vm, fmt.Errorf("unsupported input format: %s", fromFormat)))
	}

	//fmt.Println(string(dataBytes), fromFormat, toFormat)

	switch toFormat {
	case "hex":
		// 将数据转换为 HEX 编码格式
		//fmt.Println("hex", hex.EncodeToString(dataBytes), fromFormat, toFormat)
		return hex.EncodeToString(dataBytes)
	case "base64":
		// 将数据转换为 Base64 编码格式
		return base64.StdEncoding.EncodeToString(dataBytes)
	case "bytes", "binary":
		// 不需要进行转换
		return dataBytes
	case "utf8", "utf-8", "":
		// 将数据转换为 UTF-8 编码格式
		return string(dataBytes)
	default:
		panic(Error(vm, fmt.Errorf("unsupported output format: %s", toFormat)))
	}
}

// panic: interface conversion: interface {} is *cipher.cfb, not *core.AesCipher [recovered]
// panic: interface conversion: interface {} is *cipher.cfb, not *core.AesCipher

// goroutine 15 [running]:
// github.com/dop251/goja.(*Runtime).RunProgram.func1()
// C:/Users/Administrator/go/pkg/mod/github.com/dop251/goja@v0.0.0-20230605162241-28ee0ee714f3/runtime.go:1491 +0x3b8
// panic({0x1a5f6e0, 0xc0017a4780})
// C:/Program Files/Go/src/runtime/panic.go:884 +0x213
// github.com/dop251/goja.(*vm).handleThrow(0xc001312240, {0x1a5f6e0, 0xc0017a4780})
// C:/Users/Administrator/go/pkg/mod/github.com/dop251/goja@v0.0.0-20230605162241-28ee0ee714f3/vm.go:788 +0x497
// github.com/dop251/goja.(*vm).runTryInner.func1()
// C:/Users/Administrator/go/pkg/mod/github.com/dop251/goja@v0.0.0-20230605162241-28ee0ee714f3/vm.go:830 +0x45
// panic({0x1a5f6e0, 0xc0017a4780})
// C:/Program Files/Go/src/runtime/panic.go:884 +0x213
// github.com/cdle/sillyplus/core.(*Hmac).Final(0x2?, {0xc0008266d4?, 0x1?})
// C:/Users/Administrator/Desktop/sillyplus/core/node_ crypto.go:90 +0x145
// reflect.Value.call({0x1a8dfc0?, 0xc0017a43c0?, 0x1c5efb2f198?}, {0x1b85371, 0x4}, {0xc0009fc060, 0x1, 0x0?})
// C:/Program Files/Go/src/reflect/value.go:586 +0xb0b
// reflect.Value.Call({0x1a8dfc0?, 0xc0017a43c0?, 0xc000f687c0?}, {0xc0009fc060?, 0xc001076630?, 0x0?})
// C:/Program Files/Go/src/reflect/value.go:370 +0xbc
// github.com/dop251/goja.(*Runtime).wrapReflectFunc.func1({{0x2139e58, 0xc0017a43f0}, {0xc000110aa0, 0x1, 0x46}})
// C:/Users/Administrator/go/pkg/mod/github.com/dop251/goja@v0.0.0-20230605162241-28ee0ee714f3/runtime.go:2056 +0x3dc
// github.com/dop251/goja.(*nativeFuncObject).vmCall(0xc001784780, 0xc001312240, 0x1)
// C:/Users/Administrator/go/pkg/mod/github.com/dop251/goja@v0.0.0-20230605162241-28ee0ee714f3/func.go:559 +0x1af
// github.com/dop251/goja.call.exec(0x5?, 0xc001312240)
// C:/Users/Administrator/go/pkg/mod/github.com/dop251/goja@v0.0.0-20230605162241-28ee0ee714f3/vm.go:3366 +0x6a
// github.com/dop251/goja.(*vm).run(0xc001312240)
// C:/Users/Administrator/go/pkg/mod/github.com/dop251/goja@v0.0.0-20230605162241-28ee0ee714f3/vm.go:582 +0x62
// github.com/dop251/goja.(*vm).runTryInner(0x1b16220?)
// C:/Users/Administrator/go/pkg/mod/github.com/dop251/goja@v0.0.0-20230605162241-28ee0ee714f3/vm.go:834 +0x70
// github.com/dop251/goja.(*vm).runTry(0xc001312240)
// C:/Users/Administrator/go/pkg/mod/github.com/dop251/goja@v0.0.0-20230605162241-28ee0ee714f3/vm.go:820 +0x1c9
// github.com/dop251/goja.(*Runtime).RunProgram(0xc000b89c00, 0xc000ef1f20)
// C:/Users/Administrator/go/pkg/mod/github.com/dop251/goja@v0.0.0-20230605162241-28ee0ee714f3/runtime.go:1513 +0x48d
// github.com/cdle/sillyplus/core.initPlugin.func3.1(0xc000b89c00)
// C:/Users/Administrator/Desktop/sillyplus/core/plugin_core.go:585 +0x61d
// github.com/dop251/goja_nodejs/eventloop.(*EventLoop).Run(0xc001583b00, 0xc00126df20)
// C:/Users/Administrator/go/pkg/mod/github.com/dop251/goja_nodejs@v0.0.0-20230602164024-804a84515562/eventloop/eventloop.go:203 +0x37
// github.com/cdle/sillyplus/core.initPlugin.func3({0x2141bb8, 0xc001312120}, 0x0)
// C:/Users/Administrator/Desktop/sillyplus/core/plugin_core.go:511 +0x126
// github.com/cdle/sillyplus/core.AddCommand.func1(0xc000ac1d40)
// C:/Users/Administrator/Desktop/sillyplus/core/function.go:372 +0xcb
// created by github.com/cdle/sillyplus/core.AddCommand
// C:/Users/Administrator/Desktop/sillyplus/core/function.go:369 +0xfe
