package security

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"golang.org/x/crypto/pbkdf2"
)

const (
	saltSize   = 32
	iterations = 100000
	keySize    = 32
)

// GetMachineKey генерирует ключ на основе уникальных характеристик машины
func GetMachineKey() ([]byte, error) {
	// Используем hostname и другие системные параметры
	hostname, err := os.Hostname()
	if err != nil {
		return nil, err
	}

	// Добавляем путь к исполняемому файлу для уникальности
	exePath, err := os.Executable()
	if err != nil {
		return nil, err
	}

	// Комбинируем для создания уникального ключа
	combined := hostname + exePath
	hash := sha256.Sum256([]byte(combined))
	return hash[:], nil
}

// Encrypt шифрует данные с использованием AES-256-GCM
func Encrypt(plaintext []byte, password string) (string, error) {
	// Генерируем соль
	salt := make([]byte, saltSize)
	if _, err := io.ReadFull(rand.Reader, salt); err != nil {
		return "", err
	}

	// Получаем машинный ключ
	machineKey, err := GetMachineKey()
	if err != nil {
		return "", err
	}

	// Комбинируем пароль с машинным ключом
	combinedPassword := append([]byte(password), machineKey...)

	// Генерируем ключ шифрования
	key := pbkdf2.Key(combinedPassword, salt, iterations, keySize, sha256.New)

	// Создаем AES cipher
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", err
	}

	// Используем GCM для аутентифицированного шифрования
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}

	// Генерируем nonce
	nonce := make([]byte, gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return "", err
	}

	// Шифруем
	ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)

	// Комбинируем соль и зашифрованные данные
	result := append(salt, ciphertext...)

	return base64.StdEncoding.EncodeToString(result), nil
}

// Decrypt расшифровывает данные
func Decrypt(encryptedData string, password string) ([]byte, error) {
	// Декодируем из base64
	data, err := base64.StdEncoding.DecodeString(encryptedData)
	if err != nil {
		return nil, err
	}

	if len(data) < saltSize {
		return nil, fmt.Errorf("неверный формат зашифрованных данных")
	}

	// Извлекаем соль
	salt := data[:saltSize]
	ciphertext := data[saltSize:]

	// Получаем машинный ключ
	machineKey, err := GetMachineKey()
	if err != nil {
		return nil, err
	}

	// Комбинируем пароль с машинным ключом
	combinedPassword := append([]byte(password), machineKey...)

	// Генерируем ключ
	key := pbkdf2.Key(combinedPassword, salt, iterations, keySize, sha256.New)

	// Создаем AES cipher
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	// Используем GCM
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, err
	}

	nonceSize := gcm.NonceSize()
	if len(ciphertext) < nonceSize {
		return nil, fmt.Errorf("неверный размер зашифрованных данных")
	}

	nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]

	// Расшифровываем
	plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return nil, fmt.Errorf("ошибка расшифровки: неверный пароль или поврежденные данные")
	}

	return plaintext, nil
}

// SecureDelete безопасно удаляет файл (перезаписывает случайными данными)
func SecureDelete(filepath string) error {
	// Получаем размер файла
	info, err := os.Stat(filepath)
	if err != nil {
		return err
	}

	size := info.Size()

	// Открываем файл для записи
	file, err := os.OpenFile(filepath, os.O_WRONLY, 0)
	if err != nil {
		return err
	}
	defer file.Close()

	// Перезаписываем случайными данными 3 раза
	for i := 0; i < 3; i++ {
		randomData := make([]byte, size)
		if _, err := rand.Read(randomData); err != nil {
			return err
		}

		if _, err := file.WriteAt(randomData, 0); err != nil {
			return err
		}

		if err := file.Sync(); err != nil {
			return err
		}
	}

	// Удаляем файл
	return os.Remove(filepath)
}

// SetFilePermissions устанавливает строгие права доступа к файлу
func SetFilePermissions(path string) error {
	// Только владелец может читать и писать
	return os.Chmod(path, 0600)
}

// ProtectDirectory защищает директорию и все файлы в ней
func ProtectDirectory(dirPath string) error {
	// Устанавливаем права на директорию
	if err := os.Chmod(dirPath, 0700); err != nil {
		return err
	}

	// Защищаем все файлы в директории
	entries, err := os.ReadDir(dirPath)
	if err != nil {
		return err
	}

	for _, entry := range entries {
		if !entry.IsDir() {
			filePath := filepath.Join(dirPath, entry.Name())
			if err := SetFilePermissions(filePath); err != nil {
				return err
			}
		}
	}

	return nil
}
