// Copyright (c) Acrosync LLC. All rights reserved.
// Free for personal use and commercial trial
// Commercial use requires per-user licenses available from https://duplicacy.com

package duplicacy

import (
	"encoding/json"
	"io/ioutil"
	"syscall"
	"unsafe"
)

var keyringFile string

var (
	dllcrypt32  = syscall.NewLazyDLL("Crypt32.dll")
	dllkernel32 = syscall.NewLazyDLL("Kernel32.dll")

	procEncryptData = dllcrypt32.NewProc("CryptProtectData")
	procDecryptData = dllcrypt32.NewProc("CryptUnprotectData")
	procLocalFree   = dllkernel32.NewProc("LocalFree")
)

type DATA_BLOB struct {
	cbData uint32
	pbData *byte
}

func SetKeyringFile(path string) {
	keyringFile = path
}

func keyringEncrypt(value []byte) ([]byte, error) {

	dataIn := DATA_BLOB{
		pbData: &value[0],
		cbData: uint32(len(value)),
	}
	dataOut := DATA_BLOB{}

	r, _, err := procEncryptData.Call(uintptr(unsafe.Pointer(&dataIn)),
		0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&dataOut)))
	if r == 0 {
		return nil, err
	}

	address := uintptr(unsafe.Pointer(dataOut.pbData))
	defer procLocalFree.Call(address)

	encryptedData := make([]byte, dataOut.cbData)
	for i := 0; i < len(encryptedData); i++ {
		encryptedData[i] = *(*byte)(unsafe.Pointer(uintptr(int(address) + i)))
	}
	return encryptedData, nil
}

func keyringDecrypt(value []byte) ([]byte, error) {

	dataIn := DATA_BLOB{
		pbData: &value[0],
		cbData: uint32(len(value)),
	}
	dataOut := DATA_BLOB{}

	r, _, err := procDecryptData.Call(uintptr(unsafe.Pointer(&dataIn)),
		0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&dataOut)))
	if r == 0 {
		return nil, err
	}

	address := uintptr(unsafe.Pointer(dataOut.pbData))
	defer procLocalFree.Call(address)

	decryptedData := make([]byte, dataOut.cbData)
	for i := 0; i < len(decryptedData); i++ {
		address := int(uintptr(unsafe.Pointer(dataOut.pbData)))
		decryptedData[i] = *(*byte)(unsafe.Pointer(uintptr(int(address) + i)))
	}
	return decryptedData, nil
}

func keyringGet(key string) (value string) {
	if keyringFile == "" {
		LOG_DEBUG("KEYRING_NOT_INITIALIZED", "Keyring file not set")
		return ""
	}

	description, err := ioutil.ReadFile(keyringFile)
	if err != nil {
		LOG_DEBUG("KEYRING_READ", "Keyring file not read: %v", err)
		return ""
	}

	var keyring map[string][]byte
	err = json.Unmarshal(description, &keyring)
	if err != nil {
		LOG_DEBUG("KEYRING_PARSE", "Failed to parse the keyring storage file %s: %v", keyringFile, err)
		return ""
	}

	encryptedValue := keyring[key]

	if len(encryptedValue) == 0 {
		return ""
	}

	valueInBytes, err := keyringDecrypt(encryptedValue)
	if err != nil {
		LOG_DEBUG("KEYRING_DECRYPT", "Failed to decrypt the value: %v", err)
		return ""
	}

	return string(valueInBytes)
}

func keyringSet(key string, value string) bool {
	if value == "" {
		return false
	}
	if keyringFile == "" {
		LOG_DEBUG("KEYRING_NOT_INITIALIZED", "Keyring file not set")
		return false
	}

	keyring := make(map[string][]byte)

	description, err := ioutil.ReadFile(keyringFile)
	if err == nil {
		err = json.Unmarshal(description, &keyring)
		if err != nil {
			LOG_DEBUG("KEYRING_PARSE", "Failed to parse the keyring storage file %s: %v", keyringFile, err)
		}
	}

	if value == "" {
		keyring[key] = nil
	} else {

		// Check if the value to be set is the same as the existing one
		existingEncryptedValue := keyring[key]
		if len(existingEncryptedValue) > 0 {
			existingValue, err := keyringDecrypt(existingEncryptedValue)
			if err == nil && string(existingValue) == value {
				return true
			}
		}

		encryptedValue, err := keyringEncrypt([]byte(value))
		if err != nil {
			LOG_DEBUG("KEYRING_ENCRYPT", "Failed to encrypt the value: %v", err)
			return false
		}
		keyring[key] = encryptedValue
	}

	description, err = json.MarshalIndent(keyring, "", "    ")
	if err != nil {
		LOG_DEBUG("KEYRING_MARSHAL", "Failed to marshal the keyring storage: %v", err)
		return false
	}

	err = ioutil.WriteFile(keyringFile, description, 0600)
	if err != nil {
		LOG_DEBUG("KEYRING_WRITE", "Failed to save the keyring storage to file %s: %v", keyringFile, err)
		return false
	}

	return true
}
