package vault

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"path/filepath"

	"git.eeqj.de/sneak/secret/internal/secret"
	"git.eeqj.de/sneak/secret/pkg/agehd"
	"github.com/spf13/afero"
)

// Alias the metadata types from secret package for convenience
type (
	VaultMetadata    = secret.VaultMetadata
	UnlockerMetadata = secret.UnlockerMetadata
	SecretMetadata   = secret.Metadata
	Configuration    = secret.Configuration
)

// ComputeDoubleSHA256 computes the double SHA256 hash of data and returns it as hex
func ComputeDoubleSHA256(data []byte) string {
	firstHash := sha256.Sum256(data)
	secondHash := sha256.Sum256(firstHash[:])
	return hex.EncodeToString(secondHash[:])
}

// GetNextDerivationIndex finds the next available derivation index for a given mnemonic
// by deriving the public key for index 0 and using its hash to identify related vaults
func GetNextDerivationIndex(fs afero.Fs, stateDir string, mnemonic string) (uint32, error) {
	// First, derive the public key for index 0 to get our identifier
	identity0, err := agehd.DeriveIdentity(mnemonic, 0)
	if err != nil {
		return 0, fmt.Errorf("failed to derive identity for index 0: %w", err)
	}
	pubKeyHash := ComputeDoubleSHA256([]byte(identity0.Recipient().String()))

	vaultsDir := filepath.Join(stateDir, "vaults.d")

	// Check if vaults directory exists
	exists, err := afero.DirExists(fs, vaultsDir)
	if err != nil {
		return 0, fmt.Errorf("failed to check if vaults directory exists: %w", err)
	}
	if !exists {
		// No vaults yet, start with index 0
		return 0, nil
	}

	// Read all vault directories
	entries, err := afero.ReadDir(fs, vaultsDir)
	if err != nil {
		return 0, fmt.Errorf("failed to read vaults directory: %w", err)
	}

	// Track which indices are in use for this mnemonic
	usedIndices := make(map[uint32]bool)

	for _, entry := range entries {
		if !entry.IsDir() {
			continue
		}

		// Try to read vault metadata
		metadataPath := filepath.Join(vaultsDir, entry.Name(), "vault-metadata.json")
		metadataBytes, err := afero.ReadFile(fs, metadataPath)
		if err != nil {
			// Skip vaults without metadata
			continue
		}

		var metadata VaultMetadata
		if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
			// Skip vaults with invalid metadata
			continue
		}

		// Check if this vault uses the same mnemonic by comparing family hashes
		if metadata.MnemonicFamilyHash == pubKeyHash {
			usedIndices[metadata.DerivationIndex] = true
		}
	}

	// Find the first available index
	var index uint32 = 0
	for usedIndices[index] {
		index++
	}

	return index, nil
}

// SaveVaultMetadata saves vault metadata to the vault directory
func SaveVaultMetadata(fs afero.Fs, vaultDir string, metadata *VaultMetadata) error {
	metadataPath := filepath.Join(vaultDir, "vault-metadata.json")

	metadataBytes, err := json.MarshalIndent(metadata, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal vault metadata: %w", err)
	}

	if err := afero.WriteFile(fs, metadataPath, metadataBytes, secret.FilePerms); err != nil {
		return fmt.Errorf("failed to write vault metadata: %w", err)
	}

	return nil
}

// LoadVaultMetadata loads vault metadata from the vault directory
func LoadVaultMetadata(fs afero.Fs, vaultDir string) (*VaultMetadata, error) {
	metadataPath := filepath.Join(vaultDir, "vault-metadata.json")

	metadataBytes, err := afero.ReadFile(fs, metadataPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read vault metadata: %w", err)
	}

	var metadata VaultMetadata
	if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
		return nil, fmt.Errorf("failed to unmarshal vault metadata: %w", err)
	}

	return &metadata, nil
}