package tdesktop

import (
	"bytes"
	"crypto/aes"
	"crypto/sha1" // #nosec G505
	"crypto/sha512"

	"github.com/go-faster/errors"
	"golang.org/x/crypto/pbkdf2"

	"github.com/gotd/ige"
	"github.com/gotd/td/bin"
	"github.com/gotd/td/internal/crypto"
)

// See https://github.com/telegramdesktop/tdesktop/blob/v2.9.8/Telegram/SourceFiles/storage/details/storage_file_utilities.cpp#L322.
func createLegacyLocalKey(passcode, salt []byte) (r crypto.Key) {
	iters := localEncryptNoPwdIterCount
	if len(passcode) > 0 {
		iters = localEncryptIterCount
	}

	key := pbkdf2.Key(passcode, salt, iters, len(r), sha1.New)
	copy(r[:], key)
	return r
}

// See https://github.com/telegramdesktop/tdesktop/blob/v2.9.8/Telegram/SourceFiles/storage/details/storage_file_utilities.cpp#L300.
func createLocalKey(passcode, salt []byte) (r crypto.Key) {
	iters := 1
	if len(passcode) > 0 {
		iters = kStrongIterationsCount
	}

	h := sha512.New()
	_, _ = h.Write(salt)
	_, _ = h.Write(passcode)
	_, _ = h.Write(salt)

	key := pbkdf2.Key(h.Sum(nil), salt, iters, len(r), sha512.New)
	copy(r[:], key)
	return r
}

// See https://github.com/telegramdesktop/tdesktop/blob/v2.9.8/Telegram/SourceFiles/storage/details/storage_file_utilities.cpp#L584.
func decryptLocal(encrypted []byte, localKey crypto.Key) ([]byte, error) {
	if l := len(encrypted); l%aes.BlockSize != 0 {
		return nil, errors.Errorf("invalid length %d, must be padded to 16", l)
	}
	// Get encryptedKey.
	var msgKey bin.Int128
	n := copy(msgKey[:], encrypted)
	encrypted = encrypted[n:]

	aesKey, aesIV := crypto.OldKeys(localKey, msgKey, crypto.Server)
	cipher, err := aes.NewCipher(aesKey[:])
	if err != nil {
		return nil, errors.Wrap(err, "create cipher")
	}

	decrypted := make([]byte, len(encrypted))
	ige.DecryptBlocks(cipher, aesIV[:], decrypted, encrypted)

	if h := sha1.Sum(decrypted); !bytes.Equal(h[:16], msgKey[:]) /* #nosec G401 */ {
		return nil, errors.New("msg_key mismatch")
	}
	return decrypted, nil
}

// encryptLocal code may panic
func encryptLocal(decrypted []byte, localKey crypto.Key) ([]byte, error) {
	if l := len(decrypted); l%aes.BlockSize != 0 {
		return nil, errors.Errorf("invalid length %d, must be padded to 16", l)
	}
	// Compute encryptedKey.
	var msgKey bin.Int128
	h := sha1.Sum(decrypted) // #nosec G401
	copy(msgKey[:], h[:])

	aesKey, aesIV := crypto.OldKeys(localKey, msgKey, crypto.Server)
	cipher, err := aes.NewCipher(aesKey[:])
	if err != nil {
		return nil, errors.Wrap(err, "create cipher")
	}

	encrypted := make([]byte, 16+len(decrypted))
	copy(encrypted, msgKey[:])
	ige.EncryptBlocks(cipher, aesIV[:], encrypted[16:], decrypted)

	return encrypted, nil
}
