package services

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
)

const (
	// encryptionVersion is the current file format version byte.
	encryptionVersion byte = 0x01

	// aes256KeyLen is the required key length in bytes for AES-256.
	aes256KeyLen = 32

	// gcmNonceSize is the standard GCM nonce size (12 bytes).
	gcmNonceSize = 12

	// gcmTagSize is the standard GCM authentication tag size (16 bytes).
	gcmTagSize = 16

	// encryptedDEKSize is the encrypted DEK length: 32-byte DEK + 16-byte GCM tag.
	encryptedDEKSize = aes256KeyLen + gcmTagSize

	// headerSize is the fixed header: version(1) + KEK nonce(12) + encrypted DEK(48) + DEK nonce(12).
	headerSize = 1 + gcmNonceSize + encryptedDEKSize + gcmNonceSize
)

// EncryptionService provides AES-256-GCM envelope encryption for files at rest.
type EncryptionService struct {
	kek []byte // Key Encryption Key (32 bytes)
}

// NewEncryptionService creates an EncryptionService from a 64-character hex-encoded KEK.
func NewEncryptionService(hexKey string) (*EncryptionService, error) {
	if len(hexKey) != 64 {
		return nil, fmt.Errorf("encryption key must be exactly 64 hex characters (got %d)", len(hexKey))
	}

	kek, err := hex.DecodeString(hexKey)
	if err != nil {
		return nil, fmt.Errorf("invalid hex in encryption key: %w", err)
	}

	if len(kek) != aes256KeyLen {
		return nil, fmt.Errorf("decoded key must be %d bytes", aes256KeyLen)
	}

	return &EncryptionService{kek: kek}, nil
}

// IsEnabled returns true if the encryption service is configured and ready.
func (s *EncryptionService) IsEnabled() bool {
	return s != nil && len(s.kek) == aes256KeyLen
}

// Encrypt encrypts plaintext using envelope encryption (random DEK encrypted with KEK).
//
// File format:
//
//	[1-byte version 0x01]
//	[12-byte KEK nonce]
//	[48-byte encrypted DEK (32-byte DEK + 16-byte GCM tag)]
//	[12-byte DEK nonce]
//	[ciphertext + 16-byte GCM tag]
func (s *EncryptionService) Encrypt(plaintext []byte) ([]byte, error) {
	// Generate a random Data Encryption Key (DEK)
	dek := make([]byte, aes256KeyLen)
	if _, err := io.ReadFull(rand.Reader, dek); err != nil {
		return nil, fmt.Errorf("failed to generate DEK: %w", err)
	}

	// Encrypt the DEK with the KEK
	kekBlock, err := aes.NewCipher(s.kek)
	if err != nil {
		return nil, fmt.Errorf("failed to create KEK cipher: %w", err)
	}
	kekGCM, err := cipher.NewGCM(kekBlock)
	if err != nil {
		return nil, fmt.Errorf("failed to create KEK GCM: %w", err)
	}

	kekNonce := make([]byte, gcmNonceSize)
	if _, err := io.ReadFull(rand.Reader, kekNonce); err != nil {
		return nil, fmt.Errorf("failed to generate KEK nonce: %w", err)
	}
	encryptedDEK := kekGCM.Seal(nil, kekNonce, dek, nil)

	// Encrypt the plaintext with the DEK
	dekBlock, err := aes.NewCipher(dek)
	if err != nil {
		return nil, fmt.Errorf("failed to create DEK cipher: %w", err)
	}
	dekGCM, err := cipher.NewGCM(dekBlock)
	if err != nil {
		return nil, fmt.Errorf("failed to create DEK GCM: %w", err)
	}

	dekNonce := make([]byte, gcmNonceSize)
	if _, err := io.ReadFull(rand.Reader, dekNonce); err != nil {
		return nil, fmt.Errorf("failed to generate DEK nonce: %w", err)
	}
	ciphertext := dekGCM.Seal(nil, dekNonce, plaintext, nil)

	// Pack the output: version + kekNonce + encryptedDEK + dekNonce + ciphertext
	out := make([]byte, 0, headerSize+len(ciphertext))
	out = append(out, encryptionVersion)
	out = append(out, kekNonce...)
	out = append(out, encryptedDEK...)
	out = append(out, dekNonce...)
	out = append(out, ciphertext...)

	return out, nil
}

// Decrypt reverses the Encrypt operation, recovering the original plaintext.
func (s *EncryptionService) Decrypt(blob []byte) ([]byte, error) {
	if len(blob) < headerSize {
		return nil, fmt.Errorf("ciphertext too short (%d bytes, minimum %d)", len(blob), headerSize)
	}

	// Parse version
	version := blob[0]
	if version != encryptionVersion {
		return nil, fmt.Errorf("unsupported encryption version: 0x%02x", version)
	}

	offset := 1

	// Parse KEK nonce
	kekNonce := blob[offset : offset+gcmNonceSize]
	offset += gcmNonceSize

	// Parse encrypted DEK
	encryptedDEK := blob[offset : offset+encryptedDEKSize]
	offset += encryptedDEKSize

	// Parse DEK nonce
	dekNonce := blob[offset : offset+gcmNonceSize]
	offset += gcmNonceSize

	// Remaining bytes are the ciphertext + GCM tag
	ciphertext := blob[offset:]

	// Decrypt the DEK with the KEK
	kekBlock, err := aes.NewCipher(s.kek)
	if err != nil {
		return nil, fmt.Errorf("failed to create KEK cipher: %w", err)
	}
	kekGCM, err := cipher.NewGCM(kekBlock)
	if err != nil {
		return nil, fmt.Errorf("failed to create KEK GCM: %w", err)
	}

	dek, err := kekGCM.Open(nil, kekNonce, encryptedDEK, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt DEK (wrong key?): %w", err)
	}

	// Decrypt the plaintext with the DEK
	dekBlock, err := aes.NewCipher(dek)
	if err != nil {
		return nil, fmt.Errorf("failed to create DEK cipher: %w", err)
	}
	dekGCM, err := cipher.NewGCM(dekBlock)
	if err != nil {
		return nil, fmt.Errorf("failed to create DEK GCM: %w", err)
	}

	plaintext, err := dekGCM.Open(nil, dekNonce, ciphertext, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt data (tampered?): %w", err)
	}

	return plaintext, nil
}
