# SPDX-License-Identifier: Apache-2.0
# Copyright 2025 AES Encryption Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
)

// AESEncryption provides secure AES encryption and decryption functionality
type AESEncryption struct {
	key []byte
}

// NewAESEncryption creates a new AES encryption instance with a 256-bit key
// The password is hashed using SHA-256 to create a consistent key
func NewAESEncryption(password string) *AESEncryption {
	hash := sha256.Sum256([]byte(password))
	return &AESEncryption{
		key: hash[:],
	}
}

// NewAESEncryptionWithKey creates a new AES encryption instance with a provided key
// The key must be 16, 24, or 32 bytes for AES-128, AES-192, or AES-256 respectively
func NewAESEncryptionWithKey(key []byte) (*AESEncryption, error) {
	if len(key) != 16 && len(key) != 24 && len(key) != 32 {
		return nil, errors.New("key must be 16, 24, or 32 bytes long")
	}
	
	// Create a copy to prevent external modification
	keyCopy := make([]byte, len(key))
	copy(keyCopy, key)
	
	return &AESEncryption{
		key: keyCopy,
	}, nil
}

// Encrypt encrypts plaintext using AES-GCM mode
// Returns base64-encoded ciphertext with nonce prepended
func (a *AESEncryption) Encrypt(plaintext string) (string, error) {
	if plaintext == "" {
		return "", errors.New("plaintext cannot be empty")
	}

	// Create cipher block
	block, err := aes.NewCipher(a.key)
	if err != nil {
		return "", fmt.Errorf("failed to create cipher: %w", err)
	}

	// Create GCM mode
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", fmt.Errorf("failed to create GCM: %w", err)
	}

	// Generate random nonce
	nonce := make([]byte, gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return "", fmt.Errorf("failed to generate nonce: %w", err)
	}

	// Encrypt the data
	ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)

	// Return base64 encoded result
	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// Decrypt decrypts base64-encoded ciphertext using AES-GCM mode
// Expects the nonce to be prepended to the ciphertext
func (a *AESEncryption) Decrypt(encodedCiphertext string) (string, error) {
	if encodedCiphertext == "" {
		return "", errors.New("ciphertext cannot be empty")
	}

	// Decode base64
	ciphertext, err := base64.StdEncoding.DecodeString(encodedCiphertext)
	if err != nil {
		return "", fmt.Errorf("failed to decode base64: %w", err)
	}

	// Create cipher block
	block, err := aes.NewCipher(a.key)
	if err != nil {
		return "", fmt.Errorf("failed to create cipher: %w", err)
	}

	// Create GCM mode
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", fmt.Errorf("failed to create GCM: %w", err)
	}

	// Check minimum length
	nonceSize := gcm.NonceSize()
	if len(ciphertext) < nonceSize {
		return "", errors.New("ciphertext too short")
	}

	// Extract nonce and ciphertext
	nonce := ciphertext[:nonceSize]
	ciphertext = ciphertext[nonceSize:]

	// Decrypt the data
	plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return "", fmt.Errorf("failed to decrypt: %w", err)
	}

	return string(plaintext), nil
}

// EncryptBytes encrypts byte data using AES-GCM mode
// Returns encrypted bytes with nonce prepended
func (a *AESEncryption) EncryptBytes(data []byte) ([]byte, error) {
	if len(data) == 0 {
		return nil, errors.New("data cannot be empty")
	}

	// Create cipher block
	block, err := aes.NewCipher(a.key)
	if err != nil {
		return nil, fmt.Errorf("failed to create cipher: %w", err)
	}

	// Create GCM mode
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("failed to create GCM: %w", err)
	}

	// Generate random nonce
	nonce := make([]byte, gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return nil, fmt.Errorf("failed to generate nonce: %w", err)
	}

	// Encrypt the data
	ciphertext := gcm.Seal(nonce, nonce, data, nil)

	return ciphertext, nil
}

// DecryptBytes decrypts byte data using AES-GCM mode
// Expects the nonce to be prepended to the ciphertext
func (a *AESEncryption) DecryptBytes(ciphertext []byte) ([]byte, error) {
	if len(ciphertext) == 0 {
		return nil, errors.New("ciphertext cannot be empty")
	}

	// Create cipher block
	block, err := aes.NewCipher(a.key)
	if err != nil {
		return nil, fmt.Errorf("failed to create cipher: %w", err)
	}

	// Create GCM mode
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("failed to create GCM: %w", err)
	}

	// Check minimum length
	nonceSize := gcm.NonceSize()
	if len(ciphertext) < nonceSize {
		return nil, errors.New("ciphertext too short")
	}

	// Extract nonce and ciphertext
	nonce := ciphertext[:nonceSize]
	ciphertext = ciphertext[nonceSize:]

	// Decrypt the data
	plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt: %w", err)
	}

	return plaintext, nil
}

// GenerateKey generates a cryptographically secure random key of specified length
func GenerateKey(keySize int) ([]byte, error) {
	if keySize != 16 && keySize != 24 && keySize != 32 {
		return nil, errors.New("key size must be 16, 24, or 32 bytes")
	}

	key := make([]byte, keySize)
	if _, err := io.ReadFull(rand.Reader, key); err != nil {
		return nil, fmt.Errorf("failed to generate key: %w", err)
	}

	return key, nil
}

// Example usage and demonstration
func main() {
	// Example 1: Using password-based encryption
	fmt.Println("=== AES Encryption Demo ===")
	
	password := "my-secret-password-123"
	aesEnc := NewAESEncryption(password)
	
	plaintext := "Hello, World! This is a secret message."
	fmt.Printf("Original text: %s\n", plaintext)
	
	// Encrypt
	encrypted, err := aesEnc.Encrypt(plaintext)
	if err != nil {
		fmt.Printf("Encryption error: %v\n", err)
		return
	}
	fmt.Printf("Encrypted (base64): %s\n", encrypted)
	
	// Decrypt
	decrypted, err := aesEnc.Decrypt(encrypted)
	if err != nil {
		fmt.Printf("Decryption error: %v\n", err)
		return
	}
	fmt.Printf("Decrypted text: %s\n", decrypted)
	
	// Verify
	if plaintext == decrypted {
		fmt.Println("✓ Encryption/Decryption successful!")
	} else {
		fmt.Println("✗ Encryption/Decryption failed!")
	}
	
	fmt.Println()
	
	// Example 2: Using generated key
	fmt.Println("=== Key-based Encryption Demo ===")
	
	// Generate a 256-bit (32-byte) key
	key, err := GenerateKey(32)
	if err != nil {
		fmt.Printf("Key generation error: %v\n", err)
		return
	}
	
	keyBasedEnc, err := NewAESEncryptionWithKey(key)
	if err != nil {
		fmt.Printf("AES initialization error: %v\n", err)
		return
	}
	
	data := []byte("Sensitive binary data that needs encryption")
	fmt.Printf("Original data: %s\n", string(data))
	
	// Encrypt bytes
	encryptedBytes, err := keyBasedEnc.EncryptBytes(data)
	if err != nil {
		fmt.Printf("Byte encryption error: %v\n", err)
		return
	}
	fmt.Printf("Encrypted bytes (base64): %s\n", base64.StdEncoding.EncodeToString(encryptedBytes))
	
	// Decrypt bytes
	decryptedBytes, err := keyBasedEnc.DecryptBytes(encryptedBytes)
	if err != nil {
		fmt.Printf("Byte decryption error: %v\n", err)
		return
	}
	fmt.Printf("Decrypted data: %s\n", string(decryptedBytes))
	
	// Verify
	if string(data) == string(decryptedBytes) {
		fmt.Println("✓ Byte encryption/decryption successful!")
	} else {
		fmt.Println("✗ Byte encryption/decryption failed!")
	}
	
	fmt.Println()
	fmt.Println("=== Security Features ===")
	fmt.Println("• Uses AES-GCM mode for authenticated encryption")
	fmt.Println("• Generates random nonce for each encryption")
	fmt.Println("• Supports AES-128, AES-192, and AES-256")
	fmt.Println("• Password-based key derivation using SHA-256")
	fmt.Println("• Secure random key generation")
	fmt.Println("• Comprehensive error handling")
}