package storage_test

import (
	"bytes"
	"testing"

	"github.com/salvacybersec/keyhunter/pkg/storage"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestDBOpen(t *testing.T) {
	db, err := storage.Open(":memory:")
	require.NoError(t, err)
	defer db.Close()

	// Verify schema tables exist
	rows, err := db.SQL().Query("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
	require.NoError(t, err)
	defer rows.Close()

	var tables []string
	for rows.Next() {
		var name string
		require.NoError(t, rows.Scan(&name))
		tables = append(tables, name)
	}
	assert.Contains(t, tables, "findings")
	assert.Contains(t, tables, "scans")
	assert.Contains(t, tables, "settings")
}

func TestEncryptDecryptRoundtrip(t *testing.T) {
	key := make([]byte, 32) // all-zero key for test
	for i := range key {
		key[i] = byte(i)
	}
	plaintext := []byte("sk-proj-supersecretapikey1234")

	ciphertext, err := storage.Encrypt(plaintext, key)
	require.NoError(t, err)
	assert.Greater(t, len(ciphertext), len(plaintext), "ciphertext should be longer than plaintext")

	recovered, err := storage.Decrypt(ciphertext, key)
	require.NoError(t, err)
	assert.Equal(t, plaintext, recovered)
}

func TestEncryptNonDeterministic(t *testing.T) {
	key := make([]byte, 32)
	plain := []byte("test-key")
	ct1, err1 := storage.Encrypt(plain, key)
	ct2, err2 := storage.Encrypt(plain, key)
	require.NoError(t, err1)
	require.NoError(t, err2)
	assert.NotEqual(t, ct1, ct2, "same plaintext encrypted twice should produce different ciphertext")
}

func TestDecryptWrongKey(t *testing.T) {
	key1 := make([]byte, 32)
	key2 := make([]byte, 32)
	key2[0] = 0xFF

	ct, err := storage.Encrypt([]byte("secret"), key1)
	require.NoError(t, err)

	_, err = storage.Decrypt(ct, key2)
	assert.Error(t, err, "decryption with wrong key should fail")
}

func TestArgon2KeyDerivation(t *testing.T) {
	passphrase := []byte("my-secure-passphrase")
	salt := []byte("1234567890abcdef") // 16 bytes

	key1 := storage.DeriveKey(passphrase, salt)
	key2 := storage.DeriveKey(passphrase, salt)

	assert.Equal(t, 32, len(key1), "derived key must be 32 bytes")
	assert.Equal(t, key1, key2, "same passphrase+salt must produce same key")
}

func TestNewSalt(t *testing.T) {
	salt1, err1 := storage.NewSalt()
	salt2, err2 := storage.NewSalt()
	require.NoError(t, err1)
	require.NoError(t, err2)
	assert.Equal(t, 16, len(salt1))
	assert.NotEqual(t, salt1, salt2, "two salts should differ")
}

func TestSaveFindingEncrypted(t *testing.T) {
	db, err := storage.Open(":memory:")
	require.NoError(t, err)
	defer db.Close()

	// Derive a test key
	key := storage.DeriveKey([]byte("testpassphrase"), []byte("testsalt1234xxxx"))

	plainKey := "sk-proj-test1234567890abcdefghijklmnopqr"
	f := storage.Finding{
		ProviderName: "openai",
		KeyValue:     plainKey,
		Confidence:   "high",
		SourcePath:   "/test/file.env",
		SourceType:   "file",
		LineNumber:   42,
	}

	id, err := db.SaveFinding(f, key)
	require.NoError(t, err)
	assert.Greater(t, id, int64(0))

	findings, err := db.ListFindings(key)
	require.NoError(t, err)
	require.Len(t, findings, 1)
	assert.Equal(t, plainKey, findings[0].KeyValue)
	assert.Equal(t, "openai", findings[0].ProviderName)
	// Verify masking
	assert.Equal(t, "sk-proj-...opqr", findings[0].KeyMasked)

	// Verify encryption contract: raw BLOB bytes in the database must NOT contain the plaintext key.
	// This confirms Encrypt() was called before INSERT, not that the key was stored verbatim.
	var rawBlob []byte
	require.NoError(t, db.SQL().QueryRow("SELECT key_value FROM findings WHERE id = ?", id).Scan(&rawBlob))
	assert.False(t, bytes.Contains(rawBlob, []byte(plainKey)),
		"raw database BLOB must not contain plaintext key — encryption was not applied")
}
