package vault

import (
	"testing"

	"path/filepath"

	"strings"

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

func TestVaultMetadata(t *testing.T) {
	fs := afero.NewMemMapFs()
	stateDir := "/test/state"

	// Test mnemonic for consistent testing
	testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"

	t.Run("ComputeDoubleSHA256", func(t *testing.T) {
		// Test data
		data := []byte("test data")
		hash := ComputeDoubleSHA256(data)

		// Verify it's a valid hex string of 64 characters (32 bytes * 2)
		if len(hash) != 64 {
			t.Errorf("Expected hash length of 64, got %d", len(hash))
		}

		// Verify consistency
		hash2 := ComputeDoubleSHA256(data)
		if hash != hash2 {
			t.Errorf("Hash should be consistent for same input")
		}

		// Verify different input produces different hash
		hash3 := ComputeDoubleSHA256([]byte("different data"))
		if hash == hash3 {
			t.Errorf("Different input should produce different hash")
		}
	})

	t.Run("GetNextDerivationIndex", func(t *testing.T) {
		// Test with no existing vaults
		index, err := GetNextDerivationIndex(fs, stateDir, testMnemonic)
		if err != nil {
			t.Fatalf("Failed to get derivation index: %v", err)
		}
		if index != 0 {
			t.Errorf("Expected index 0 for first vault, got %d", index)
		}

		// Create a vault with metadata and matching public key
		vaultDir := filepath.Join(stateDir, "vaults.d", "vault1")
		if err := fs.MkdirAll(vaultDir, 0700); err != nil {
			t.Fatalf("Failed to create vault directory: %v", err)
		}

		// Derive identity for index 0
		identity0, err := agehd.DeriveIdentity(testMnemonic, 0)
		if err != nil {
			t.Fatalf("Failed to derive identity: %v", err)
		}
		pubKey0 := identity0.Recipient().String()
		pubKeyHash0 := ComputeDoubleSHA256([]byte(pubKey0))

		// Write public key
		if err := afero.WriteFile(fs, filepath.Join(vaultDir, "pub.age"), []byte(pubKey0), 0600); err != nil {
			t.Fatalf("Failed to write public key: %v", err)
		}

		metadata1 := &VaultMetadata{
			DerivationIndex: 0,
			PublicKeyHash:   pubKeyHash0,
		}
		if err := SaveVaultMetadata(fs, vaultDir, metadata1); err != nil {
			t.Fatalf("Failed to save metadata: %v", err)
		}

		// Next index for same mnemonic should be 1
		index, err = GetNextDerivationIndex(fs, stateDir, testMnemonic)
		if err != nil {
			t.Fatalf("Failed to get derivation index: %v", err)
		}
		if index != 1 {
			t.Errorf("Expected index 1 for second vault with same mnemonic, got %d", index)
		}

		// Different mnemonic should start at 0
		differentMnemonic := "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong"
		index, err = GetNextDerivationIndex(fs, stateDir, differentMnemonic)
		if err != nil {
			t.Fatalf("Failed to get derivation index: %v", err)
		}
		if index != 0 {
			t.Errorf("Expected index 0 for first vault with different mnemonic, got %d", index)
		}

		// Add another vault with same mnemonic but higher index
		vaultDir2 := filepath.Join(stateDir, "vaults.d", "vault2")
		if err := fs.MkdirAll(vaultDir2, 0700); err != nil {
			t.Fatalf("Failed to create vault directory: %v", err)
		}

		// Derive identity for index 5
		identity5, err := agehd.DeriveIdentity(testMnemonic, 5)
		if err != nil {
			t.Fatalf("Failed to derive identity: %v", err)
		}
		pubKey5 := identity5.Recipient().String()

		// Write public key
		if err := afero.WriteFile(fs, filepath.Join(vaultDir2, "pub.age"), []byte(pubKey5), 0600); err != nil {
			t.Fatalf("Failed to write public key: %v", err)
		}

		metadata2 := &VaultMetadata{
			DerivationIndex: 5,
			PublicKeyHash:   pubKeyHash0, // Same hash since it's from the same mnemonic
		}
		if err := SaveVaultMetadata(fs, vaultDir2, metadata2); err != nil {
			t.Fatalf("Failed to save metadata: %v", err)
		}

		// Next index should be 1 (not 6) because we look for the first available slot
		index, err = GetNextDerivationIndex(fs, stateDir, testMnemonic)
		if err != nil {
			t.Fatalf("Failed to get derivation index: %v", err)
		}
		if index != 1 {
			t.Errorf("Expected index 1 (first available), got %d", index)
		}
	})

	t.Run("MetadataPersistence", func(t *testing.T) {
		vaultDir := filepath.Join(stateDir, "vaults.d", "test-vault")
		if err := fs.MkdirAll(vaultDir, 0700); err != nil {
			t.Fatalf("Failed to create vault directory: %v", err)
		}

		// Create and save metadata
		metadata := &VaultMetadata{
			DerivationIndex: 3,
			PublicKeyHash:   "test-public-key-hash",
		}

		if err := SaveVaultMetadata(fs, vaultDir, metadata); err != nil {
			t.Fatalf("Failed to save metadata: %v", err)
		}

		// Load and verify
		loaded, err := LoadVaultMetadata(fs, vaultDir)
		if err != nil {
			t.Fatalf("Failed to load metadata: %v", err)
		}

		if loaded.DerivationIndex != metadata.DerivationIndex {
			t.Errorf("DerivationIndex mismatch: expected %d, got %d", metadata.DerivationIndex, loaded.DerivationIndex)
		}
		if loaded.PublicKeyHash != metadata.PublicKeyHash {
			t.Errorf("PublicKeyHash mismatch: expected %s, got %s", metadata.PublicKeyHash, loaded.PublicKeyHash)
		}
	})

	t.Run("DifferentKeysForDifferentIndices", func(t *testing.T) {
		// Derive keys with different indices
		identity0, err := agehd.DeriveIdentity(testMnemonic, 0)
		if err != nil {
			t.Fatalf("Failed to derive identity with index 0: %v", err)
		}

		identity1, err := agehd.DeriveIdentity(testMnemonic, 1)
		if err != nil {
			t.Fatalf("Failed to derive identity with index 1: %v", err)
		}

		// Compute public key hashes
		pubKey0 := identity0.Recipient().String()
		pubKey1 := identity1.Recipient().String()
		hash0 := ComputeDoubleSHA256([]byte(pubKey0))

		// Verify different indices produce different public keys
		if pubKey0 == pubKey1 {
			t.Errorf("Different derivation indices should produce different public keys")
		}

		// But the hash of index 0's public key should be the same for the same mnemonic
		// This is what we use as the identifier
		identity0Again, _ := agehd.DeriveIdentity(testMnemonic, 0)
		pubKey0Again := identity0Again.Recipient().String()
		hash0Again := ComputeDoubleSHA256([]byte(pubKey0Again))

		if hash0 != hash0Again {
			t.Errorf("Same mnemonic should produce same public key hash for index 0")
		}
	})
}

func TestPublicKeyHashConsistency(t *testing.T) {
	// Use the same test mnemonic that the integration test uses
	testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"

	// Derive identity from index 0 multiple times
	identity1, err := agehd.DeriveIdentity(testMnemonic, 0)
	if err != nil {
		t.Fatalf("Failed to derive first identity: %v", err)
	}

	identity2, err := agehd.DeriveIdentity(testMnemonic, 0)
	if err != nil {
		t.Fatalf("Failed to derive second identity: %v", err)
	}

	// Verify identities are the same
	if identity1.Recipient().String() != identity2.Recipient().String() {
		t.Errorf("Identity derivation is not deterministic")
		t.Logf("First:  %s", identity1.Recipient().String())
		t.Logf("Second: %s", identity2.Recipient().String())
	}

	// Compute public key hashes
	hash1 := ComputeDoubleSHA256([]byte(identity1.Recipient().String()))
	hash2 := ComputeDoubleSHA256([]byte(identity2.Recipient().String()))

	// Verify hashes are the same
	if hash1 != hash2 {
		t.Errorf("Public key hash computation is not deterministic")
		t.Logf("First hash:  %s", hash1)
		t.Logf("Second hash: %s", hash2)
	}

	t.Logf("Test mnemonic public key hash (index 0): %s", hash1)
}

func TestSampleHashCalculation(t *testing.T) {
	// Test with the exact mnemonic from integration test if available
	// We'll also test with a few different mnemonics to make sure they produce different hashes
	mnemonics := []string{
		"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
		"legal winner thank year wave sausage worth useful legal winner thank yellow",
		"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong",
	}

	for i, mnemonic := range mnemonics {
		identity, err := agehd.DeriveIdentity(mnemonic, 0)
		if err != nil {
			t.Fatalf("Failed to derive identity for mnemonic %d: %v", i, err)
		}

		hash := ComputeDoubleSHA256([]byte(identity.Recipient().String()))
		t.Logf("Mnemonic %d hash (index 0): %s", i, hash)
		t.Logf("  Recipient: %s", identity.Recipient().String())
	}
}

func TestWorkflowMismatch(t *testing.T) {
	testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"

	// Create a temporary directory for testing
	tempDir := t.TempDir()
	fs := afero.NewOsFs()

	// Test Case 1: Create vault WITH mnemonic (like init command)
	t.Setenv("SB_SECRET_MNEMONIC", testMnemonic)
	_, err := CreateVault(fs, tempDir, "default")
	if err != nil {
		t.Fatalf("Failed to create vault with mnemonic: %v", err)
	}

	// Load metadata for vault1
	vault1Dir := filepath.Join(tempDir, "vaults.d", "default")
	metadata1, err := LoadVaultMetadata(fs, vault1Dir)
	if err != nil {
		t.Fatalf("Failed to load vault1 metadata: %v", err)
	}

	t.Logf("Vault1 (with mnemonic) - DerivationIndex: %d, PublicKeyHash: %s",
		metadata1.DerivationIndex, metadata1.PublicKeyHash)

	// Test Case 2: Create vault WITHOUT mnemonic, then import (like work vault)
	t.Setenv("SB_SECRET_MNEMONIC", "")
	_, err = CreateVault(fs, tempDir, "work")
	if err != nil {
		t.Fatalf("Failed to create vault without mnemonic: %v", err)
	}

	vault2Dir := filepath.Join(tempDir, "vaults.d", "work")

	// Simulate the vault import process
	t.Setenv("SB_SECRET_MNEMONIC", testMnemonic)

	// Get the next available derivation index for this mnemonic
	derivationIndex, err := GetNextDerivationIndex(fs, tempDir, testMnemonic)
	if err != nil {
		t.Fatalf("Failed to get next derivation index: %v", err)
	}

	t.Logf("Next derivation index for import: %d", derivationIndex)

	// Calculate public key hash from index 0 (same as in VaultImport)
	identity0, err := agehd.DeriveIdentity(testMnemonic, 0)
	if err != nil {
		t.Fatalf("Failed to derive identity for index 0: %v", err)
	}
	publicKeyHash := ComputeDoubleSHA256([]byte(identity0.Recipient().String()))

	// Load existing metadata and update it (same as in VaultImport)
	existingMetadata, err := LoadVaultMetadata(fs, vault2Dir)
	if err != nil {
		t.Fatalf("Failed to load existing metadata: %v", err)
	}

	// Update metadata with new derivation info
	existingMetadata.DerivationIndex = derivationIndex
	existingMetadata.PublicKeyHash = publicKeyHash

	if err := SaveVaultMetadata(fs, vault2Dir, existingMetadata); err != nil {
		t.Fatalf("Failed to save vault metadata: %v", err)
	}

	// Load updated metadata for vault2
	metadata2, err := LoadVaultMetadata(fs, vault2Dir)
	if err != nil {
		t.Fatalf("Failed to load vault2 metadata: %v", err)
	}

	t.Logf("Vault2 (imported mnemonic) - DerivationIndex: %d, PublicKeyHash: %s",
		metadata2.DerivationIndex, metadata2.PublicKeyHash)

	// Verify that both vaults have the same public key hash
	if metadata1.PublicKeyHash != metadata2.PublicKeyHash {
		t.Errorf("Public key hashes don't match!")
		t.Logf("Vault1 hash: %s", metadata1.PublicKeyHash)
		t.Logf("Vault2 hash: %s", metadata2.PublicKeyHash)
	} else {
		t.Logf("SUCCESS: Both vaults have the same public key hash: %s", metadata1.PublicKeyHash)
	}
}

func TestReverseEngineerHash(t *testing.T) {
	// This is the hash that the work vault is getting in the failing test
	wrongHash := "e34a2f500e395d8934a90a99ee9311edcfffd68cb701079575e50cbac7bb9417"
	correctHash := "992552b00b3879dfae461fab9a084b47784a032771c7a9accaebdde05ec7a7d1"

	// Test mnemonic from integration test
	testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"

	// Calculate hash for test mnemonic
	identity, err := agehd.DeriveIdentity(testMnemonic, 0)
	if err != nil {
		t.Fatalf("Failed to derive identity: %v", err)
	}

	calculatedHash := ComputeDoubleSHA256([]byte(identity.Recipient().String()))
	t.Logf("Test mnemonic hash: %s", calculatedHash)

	if calculatedHash == correctHash {
		t.Logf("✓ Test mnemonic produces the correct hash")
	} else {
		t.Errorf("✗ Test mnemonic does not produce the correct hash")
	}

	if calculatedHash == wrongHash {
		t.Logf("✗ Test mnemonic unexpectedly produces the wrong hash")
	}

	// Let's try some other possibilities - maybe there's a string normalization issue?
	variations := []string{
		"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
		" abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about ",
		"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\n",
		strings.TrimSpace("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"),
	}

	for i, variation := range variations {
		identity, err := agehd.DeriveIdentity(variation, 0)
		if err != nil {
			t.Logf("Variation %d failed: %v", i, err)
			continue
		}

		hash := ComputeDoubleSHA256([]byte(identity.Recipient().String()))
		t.Logf("Variation %d hash: %s", i, hash)

		if hash == wrongHash {
			t.Logf("✗ Found variation that produces wrong hash: '%s'", variation)
		}
	}

	// Maybe let's try an empty mnemonic or something else?
	emptyMnemonics := []string{
		"",
		" ",
	}

	for i, emptyMnemonic := range emptyMnemonics {
		identity, err := agehd.DeriveIdentity(emptyMnemonic, 0)
		if err != nil {
			t.Logf("Empty mnemonic %d failed (expected): %v", i, err)
			continue
		}

		hash := ComputeDoubleSHA256([]byte(identity.Recipient().String()))
		t.Logf("Empty mnemonic %d hash: %s", i, hash)

		if hash == wrongHash {
			t.Logf("✗ Empty mnemonic produces wrong hash!")
		}
	}
}
