package appexport

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"errors"
	"fmt"
	"io"
	"os"

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

const (
	magicHeader = "FABE"   // Felhom App Bundle Encrypted
	scryptN     = 1 << 15  // 32768
	scryptR     = 8
	scryptP     = 1
	saltSize    = 32
	aesKeySize  = 32
	hmacKeySize = 32
	ivSize      = aes.BlockSize // 16
)

// deriveKeys derives an AES-256 key and HMAC-SHA256 key from password + salt.
func deriveKeys(password string, salt []byte) (aesKey, hmacKey []byte, err error) {
	derived, err := scrypt.Key([]byte(password), salt, scryptN, scryptR, scryptP, aesKeySize+hmacKeySize)
	if err != nil {
		return nil, nil, err
	}
	return derived[:aesKeySize], derived[aesKeySize:], nil
}

// IsEncryptedFAB checks if a file starts with the "FABE" magic header.
func IsEncryptedFAB(path string) (bool, error) {
	f, err := os.Open(path)
	if err != nil {
		return false, err
	}
	defer f.Close()

	magic := make([]byte, 4)
	n, err := f.Read(magic)
	if err != nil || n < 4 {
		return false, nil
	}
	return string(magic) == magicHeader, nil
}

// EncryptFile encrypts a plaintext file with a password.
// Uses AES-256-CTR + HMAC-SHA256 with scrypt key derivation.
// Format: "FABE" (4) || salt (32) || IV (16) || encrypted_data || HMAC-SHA256 (32)
func EncryptFile(inputPath, outputPath, password string) error {
	in, err := os.Open(inputPath)
	if err != nil {
		return fmt.Errorf("open input: %w", err)
	}
	defer in.Close()

	out, err := os.Create(outputPath)
	if err != nil {
		return fmt.Errorf("create output: %w", err)
	}
	defer out.Close()

	salt := make([]byte, saltSize)
	if _, err := rand.Read(salt); err != nil {
		return fmt.Errorf("generating salt: %w", err)
	}
	iv := make([]byte, ivSize)
	if _, err := rand.Read(iv); err != nil {
		return fmt.Errorf("generating IV: %w", err)
	}

	aesKey, hmKey, err := deriveKeys(password, salt)
	if err != nil {
		return fmt.Errorf("deriving keys: %w", err)
	}

	block, err := aes.NewCipher(aesKey)
	if err != nil {
		return err
	}
	stream := cipher.NewCTR(block, iv)
	mac := hmac.New(sha256.New, hmKey)

	// Write header (magic is NOT in HMAC; salt + IV are)
	if _, err := out.Write([]byte(magicHeader)); err != nil {
		return err
	}
	mac.Write(salt)
	if _, err := out.Write(salt); err != nil {
		return err
	}
	mac.Write(iv)
	if _, err := out.Write(iv); err != nil {
		return err
	}

	// Encrypt and stream data
	buf := make([]byte, 64*1024)
	for {
		n, readErr := in.Read(buf)
		if n > 0 {
			encrypted := make([]byte, n)
			stream.XORKeyStream(encrypted, buf[:n])
			mac.Write(encrypted)
			if _, err := out.Write(encrypted); err != nil {
				return err
			}
		}
		if readErr == io.EOF {
			break
		}
		if readErr != nil {
			return fmt.Errorf("read: %w", readErr)
		}
	}

	// Append HMAC tag
	if _, err := out.Write(mac.Sum(nil)); err != nil {
		return err
	}
	return out.Sync()
}

// DecryptFile decrypts an encrypted .fab file with a password.
// Returns a clear error if the password is wrong or the file is corrupted.
func DecryptFile(inputPath, outputPath, password string) error {
	in, err := os.Open(inputPath)
	if err != nil {
		return fmt.Errorf("open input: %w", err)
	}
	defer in.Close()

	// Verify magic header
	magic := make([]byte, 4)
	if _, err := io.ReadFull(in, magic); err != nil {
		return fmt.Errorf("reading header: %w", err)
	}
	if string(magic) != magicHeader {
		return errors.New("not an encrypted FAB file")
	}

	// Read salt and IV
	salt := make([]byte, saltSize)
	if _, err := io.ReadFull(in, salt); err != nil {
		return fmt.Errorf("reading salt: %w", err)
	}
	iv := make([]byte, ivSize)
	if _, err := io.ReadFull(in, iv); err != nil {
		return fmt.Errorf("reading IV: %w", err)
	}

	// Calculate data size (total - header - HMAC tag)
	stat, err := in.Stat()
	if err != nil {
		return err
	}
	headerSize := int64(4 + saltSize + ivSize)
	tagSize := int64(sha256.Size)
	dataSize := stat.Size() - headerSize - tagSize
	if dataSize < 0 {
		return errors.New("file too small to be valid")
	}

	aesKey, hmKey, err := deriveKeys(password, salt)
	if err != nil {
		return fmt.Errorf("deriving keys: %w", err)
	}

	mac := hmac.New(sha256.New, hmKey)
	mac.Write(salt)
	mac.Write(iv)

	block, err := aes.NewCipher(aesKey)
	if err != nil {
		return err
	}
	stream := cipher.NewCTR(block, iv)

	out, err := os.Create(outputPath)
	if err != nil {
		return fmt.Errorf("create output: %w", err)
	}
	defer out.Close()

	// Decrypt data section
	buf := make([]byte, 64*1024)
	remaining := dataSize
	for remaining > 0 {
		toRead := int64(len(buf))
		if toRead > remaining {
			toRead = remaining
		}
		n, readErr := in.Read(buf[:toRead])
		if n > 0 {
			mac.Write(buf[:n])
			decrypted := make([]byte, n)
			stream.XORKeyStream(decrypted, buf[:n])
			if _, err := out.Write(decrypted); err != nil {
				return err
			}
			remaining -= int64(n)
		}
		if readErr == io.EOF {
			break
		}
		if readErr != nil {
			return fmt.Errorf("read: %w", readErr)
		}
	}

	// Verify HMAC tag
	storedMAC := make([]byte, sha256.Size)
	if _, err := io.ReadFull(in, storedMAC); err != nil {
		return fmt.Errorf("reading HMAC: %w", err)
	}
	if !hmac.Equal(mac.Sum(nil), storedMAC) {
		os.Remove(outputPath)
		return errors.New("jelszó hibás vagy a fájl sérült")
	}

	return out.Sync()
}
