package backend

import (
	"archive/zip"
	"bytes"
	"context"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"github.com/wailsapp/wails/v2/pkg/runtime"
)

type Decryptor struct {
	ctx context.Context
}

func NewDecryptor() *Decryptor {
	return &Decryptor{}
}

func (d *Decryptor) OnStartup(ctx context.Context) {
	d.ctx = ctx
	fmt.Printf("ctx: %v\n", ctx)
}

func (d *Decryptor) SelectPrivateKeyFile() (string, error) {
	return runtime.OpenFileDialog(d.ctx, runtime.OpenDialogOptions{
		Title: "Select Private Key File",
		Filters: []runtime.FileFilter{
			{DisplayName: "PEM Files", Pattern: "*.pem"},
		},
	})
}

func (d *Decryptor) SelectEncryptedFile() (string, error) {
	return runtime.OpenFileDialog(d.ctx, runtime.OpenDialogOptions{
		Title: "Select Encrypted Zip File",
		Filters: []runtime.FileFilter{
			{DisplayName: "Encrypted Zip", Pattern: "*.zip"},
		},
	})
}

func (d *Decryptor) SelectOutputDirectory() (string, error) {
	return runtime.OpenDirectoryDialog(d.ctx, runtime.OpenDialogOptions{
		Title: "Select Output Directory",
	})
}
func (d *Decryptor) DecryptHybridZip(encryptedZipPath string, privKeyPath string, outputDir string) error {
	r, err := zip.OpenReader(encryptedZipPath)
	if err != nil {
		return err
	}
	defer r.Close()

	var encData, encKey []byte
	for _, f := range r.File {
		rc, _ := f.Open()
		data, _ := io.ReadAll(rc)
		rc.Close()
		fmt.Printf("f.Name: %v\n", f.Name)
		switch f.Name {
		case "encrypted_data.bin":
			encData = data
		case "encrypted_key.bin":
			encKey = data
		}
	}
	if encData == nil || encKey == nil {
		return errors.New("missing encrypted files")
	}

	privKey, err := loadPrivateKey(privKeyPath)
	if err != nil {
		return err
	}
	aesKey, err := rsa.DecryptPKCS1v15(rand.Reader, privKey, encKey)
	if err != nil {
		return err
	}

	zipData, err := decryptAES(encData, aesKey)
	if err != nil {
		return err
	}

	zipReader, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData)))
	if err != nil {
		return err
	}
	var originalFolderName string

	for _, f := range zipReader.File {
		dirPath := filepath.Dir(f.Name)

		if len(dirPath) > 0 && originalFolderName == "" {
			originalFolderName = dirPath
		}

	}

	if originalFolderName == "" {
		return errors.New("failed to detect original folder name")
	}

	outputZip := filepath.Join(outputDir, originalFolderName+".zip")

	err = os.WriteFile(outputZip, zipData, 0644)
	if err != nil {
		return fmt.Errorf("failed to write decrypted zip file: %w", err)
	}

	return nil
}

func loadPrivateKey(path string) (*rsa.PrivateKey, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	block, _ := pem.Decode(data)
	if block == nil || block.Type != "PRIVATE KEY" {
		return nil, errors.New("invalid private key format")
	}
	priv, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return nil, err
	}
	return priv.(*rsa.PrivateKey), nil
}

func decryptAES(cipherData, key []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, err
	}
	nonceSize := gcm.NonceSize()
	if len(cipherData) < nonceSize {
		return nil, fmt.Errorf("cipher data too short")
	}
	nonce, cipherText := cipherData[:nonceSize], cipherData[nonceSize:]
	return gcm.Open(nil, nonce, cipherText, nil)
}
