package cmd

import (
	"bytes"
	"os"
	"path/filepath"
	"testing"

	"github.com/salvacybersec/keyhunter/pkg/engine"
	"github.com/salvacybersec/keyhunter/pkg/importer"
	"github.com/salvacybersec/keyhunter/pkg/storage"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestSelectImporter(t *testing.T) {
	cases := []struct {
		format  string
		want    string
		wantErr bool
	}{
		{"trufflehog", "trufflehog", false},
		{"gitleaks", "gitleaks", false},
		{"gitleaks-csv", "gitleaks-csv", false},
		{"bogus", "", true},
		{"", "", true},
	}
	for _, tc := range cases {
		t.Run(tc.format, func(t *testing.T) {
			imp, err := selectImporter(tc.format)
			if tc.wantErr {
				assert.Error(t, err)
				return
			}
			require.NoError(t, err)
			assert.Equal(t, tc.want, imp.Name())
			// Check concrete type matches expectation via interface dispatch.
			switch tc.format {
			case "trufflehog":
				_, ok := imp.(importer.TruffleHogImporter)
				assert.True(t, ok, "expected TruffleHogImporter")
			case "gitleaks":
				_, ok := imp.(importer.GitleaksImporter)
				assert.True(t, ok, "expected GitleaksImporter")
			case "gitleaks-csv":
				_, ok := imp.(importer.GitleaksCSVImporter)
				assert.True(t, ok, "expected GitleaksCSVImporter")
			}
		})
	}
}

func TestEngineToStorage(t *testing.T) {
	ef := engine.Finding{
		ProviderName:   "openai",
		KeyValue:       "sk-abcdefghijklmnop",
		KeyMasked:      "sk-abcde...mnop",
		Confidence:     "high",
		Source:         "a.yml",
		SourceType:     "import:trufflehog",
		LineNumber:     5,
		Verified:       true,
		VerifyStatus:   "live",
		VerifyHTTPCode: 200,
		VerifyMetadata: map[string]string{"org": "acme"},
	}
	sf := engineToStorage(ef)
	assert.Equal(t, "openai", sf.ProviderName)
	assert.Equal(t, "sk-abcdefghijklmnop", sf.KeyValue)
	assert.Equal(t, "sk-abcde...mnop", sf.KeyMasked)
	assert.Equal(t, "high", sf.Confidence)
	assert.Equal(t, "a.yml", sf.SourcePath, "engine.Source -> storage.SourcePath")
	assert.Equal(t, "import:trufflehog", sf.SourceType)
	assert.Equal(t, 5, sf.LineNumber)
	assert.True(t, sf.Verified)
	assert.Equal(t, "live", sf.VerifyStatus)
	assert.Equal(t, 200, sf.VerifyHTTPCode)
	assert.Equal(t, map[string]string{"org": "acme"}, sf.VerifyMetadata)
}

// seedImportDB spins up a temp SQLite database wired through viper +
// KEYHUNTER_PASSPHRASE so runImport -> openDBWithKey resolves to it.
func seedImportDB(t *testing.T) string {
	t.Helper()
	dir := t.TempDir()
	dbPath := filepath.Join(dir, "import.db")

	viper.Reset()
	viper.Set("database.path", dbPath)
	t.Setenv("KEYHUNTER_PASSPHRASE", "test-pass")

	t.Cleanup(func() {
		viper.Reset()
		importFormat = ""
	})
	return dbPath
}

func TestRunImport_TruffleHogEndToEnd(t *testing.T) {
	dbPath := seedImportDB(t)

	// Use the canonical testdata shipped by pkg/importer.
	importFormat = "trufflehog"
	samplePath, err := filepath.Abs(filepath.Join("..", "pkg", "importer", "testdata", "trufflehog-sample.json"))
	require.NoError(t, err)
	_, err = os.Stat(samplePath)
	require.NoError(t, err, "testdata trufflehog-sample.json must exist")

	var out bytes.Buffer
	importCmd.SetOut(&out)
	importCmd.SetErr(&out)

	// First import: all findings are new.
	err = runImport(importCmd, []string{samplePath})
	require.NoError(t, err)
	first := out.String()
	assert.Contains(t, first, "Imported 3 findings")
	assert.Contains(t, first, "3 new")
	assert.Contains(t, first, "0 duplicates")

	// Confirm findings landed in the database.
	db, err := storage.Open(dbPath)
	require.NoError(t, err)
	encKey, err := loadOrCreateEncKey(db, "test-pass")
	require.NoError(t, err)
	stored, err := db.ListFindings(encKey)
	require.NoError(t, err)
	assert.GreaterOrEqual(t, len(stored), 3)
	require.NoError(t, db.Close())

	// Second import of the same file: everything should now be a duplicate.
	out.Reset()
	err = runImport(importCmd, []string{samplePath})
	require.NoError(t, err)
	second := out.String()
	assert.Contains(t, second, "Imported 3 findings")
	assert.Contains(t, second, "0 new")
	assert.Contains(t, second, "3 duplicates")
}

func TestRunImport_UnknownFormat(t *testing.T) {
	_ = seedImportDB(t)
	importFormat = "bogus"
	err := runImport(importCmd, []string{"/nonexistent"})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "unknown format")
}

func TestRunImport_MissingFile(t *testing.T) {
	_ = seedImportDB(t)
	importFormat = "trufflehog"
	err := runImport(importCmd, []string{filepath.Join(t.TempDir(), "does-not-exist.json")})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "opening")
}
