// Version CLI Command Tests
//
// Tests for version-related CLI commands:
//
// - TestListVersionsCommand: Tests `secret version list` command output
// - TestListVersionsNonExistentSecret: Tests error handling for missing secrets
// - TestPromoteVersionCommand: Tests `secret version promote` command
// - TestPromoteNonExistentVersion: Tests error handling for invalid promotion
// - TestGetSecretWithVersion: Tests `secret get --version` flag functionality
// - TestVersionCommandStructure: Tests command structure and help text
// - TestListVersionsEmptyOutput: Tests edge case with no versions
//
// Test Utilities:
// - setupTestVault(): CLI test helper for vault initialization
// - Uses consistent test mnemonic for reproducible testing

package cli

import (
	"io"
	"os"
	"strings"
	"testing"
	"time"

	"path/filepath"

	"git.eeqj.de/sneak/secret/internal/secret"
	"git.eeqj.de/sneak/secret/internal/vault"
	"git.eeqj.de/sneak/secret/pkg/agehd"
	"github.com/spf13/afero"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// Helper function to set up a vault with long-term key
func setupTestVault(t *testing.T, fs afero.Fs, stateDir string) {
	// Set mnemonic for testing
	t.Setenv(secret.EnvMnemonic, "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about")

	// Create vault
	vlt, err := vault.CreateVault(fs, stateDir, "default")
	require.NoError(t, err)

	// Derive and store long-term key from mnemonic
	mnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
	ltIdentity, err := agehd.DeriveIdentity(mnemonic, 0)
	require.NoError(t, err)

	// Store long-term public key in vault
	vaultDir, _ := vlt.GetDirectory()
	ltPubKeyPath := filepath.Join(vaultDir, "pub.age")
	err = afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), 0600)
	require.NoError(t, err)

	// Select vault
	err = vault.SelectVault(fs, stateDir, "default")
	require.NoError(t, err)
}

func TestListVersionsCommand(t *testing.T) {
	fs := afero.NewMemMapFs()
	stateDir := "/test/state"
	cli := NewCLIInstanceWithStateDir(fs, stateDir)

	// Set up vault with long-term key
	setupTestVault(t, fs, stateDir)

	// Add a secret with multiple versions
	vlt, err := vault.GetCurrentVault(fs, stateDir)
	require.NoError(t, err)

	err = vlt.AddSecret("test/secret", []byte("version-1"), false)
	require.NoError(t, err)

	time.Sleep(10 * time.Millisecond)

	err = vlt.AddSecret("test/secret", []byte("version-2"), true)
	require.NoError(t, err)

	// Capture output
	oldStdout := os.Stdout
	r, w, _ := os.Pipe()
	os.Stdout = w

	// List versions
	err = cli.ListVersions("test/secret")
	require.NoError(t, err)

	// Restore stdout and read output
	w.Close()
	os.Stdout = oldStdout
	output, _ := io.ReadAll(r)
	outputStr := string(output)

	// Verify output contains version headers
	assert.Contains(t, outputStr, "VERSION")
	assert.Contains(t, outputStr, "CREATED")
	assert.Contains(t, outputStr, "STATUS")
	assert.Contains(t, outputStr, "NOT_BEFORE")
	assert.Contains(t, outputStr, "NOT_AFTER")

	// Should have current status for latest version
	assert.Contains(t, outputStr, "current")

	// Should have two version entries
	lines := strings.Split(outputStr, "\n")
	versionLines := 0
	for _, line := range lines {
		if strings.Contains(line, ".001") || strings.Contains(line, ".002") {
			versionLines++
		}
	}
	assert.Equal(t, 2, versionLines)
}

func TestListVersionsNonExistentSecret(t *testing.T) {
	fs := afero.NewMemMapFs()
	stateDir := "/test/state"
	cli := NewCLIInstanceWithStateDir(fs, stateDir)

	// Set up vault with long-term key
	setupTestVault(t, fs, stateDir)

	// Try to list versions of non-existent secret
	err := cli.ListVersions("nonexistent/secret")
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "not found")
}

func TestPromoteVersionCommand(t *testing.T) {
	fs := afero.NewMemMapFs()
	stateDir := "/test/state"
	cli := NewCLIInstanceWithStateDir(fs, stateDir)

	// Set up vault with long-term key
	setupTestVault(t, fs, stateDir)

	// Add a secret with multiple versions
	vlt, err := vault.GetCurrentVault(fs, stateDir)
	require.NoError(t, err)

	err = vlt.AddSecret("test/secret", []byte("version-1"), false)
	require.NoError(t, err)

	time.Sleep(10 * time.Millisecond)

	err = vlt.AddSecret("test/secret", []byte("version-2"), true)
	require.NoError(t, err)

	// Get versions
	vaultDir, _ := vlt.GetDirectory()
	secretDir := vaultDir + "/secrets.d/test%secret"
	versions, err := secret.ListVersions(fs, secretDir)
	require.NoError(t, err)
	require.Len(t, versions, 2)

	// Current should be version-2
	value, err := vlt.GetSecret("test/secret")
	require.NoError(t, err)
	assert.Equal(t, []byte("version-2"), value)

	// Promote first version
	firstVersion := versions[1] // Older version

	// Capture output
	oldStdout := os.Stdout
	r, w, _ := os.Pipe()
	os.Stdout = w

	err = cli.PromoteVersion("test/secret", firstVersion)
	require.NoError(t, err)

	// Restore stdout and read output
	w.Close()
	os.Stdout = oldStdout
	output, _ := io.ReadAll(r)
	outputStr := string(output)

	// Verify success message
	assert.Contains(t, outputStr, "Promoted version")
	assert.Contains(t, outputStr, firstVersion)

	// Verify current is now version-1
	value, err = vlt.GetSecret("test/secret")
	require.NoError(t, err)
	assert.Equal(t, []byte("version-1"), value)
}

func TestPromoteNonExistentVersion(t *testing.T) {
	fs := afero.NewMemMapFs()
	stateDir := "/test/state"
	cli := NewCLIInstanceWithStateDir(fs, stateDir)

	// Set up vault with long-term key
	setupTestVault(t, fs, stateDir)

	// Add a secret
	vlt, err := vault.GetCurrentVault(fs, stateDir)
	require.NoError(t, err)

	err = vlt.AddSecret("test/secret", []byte("value"), false)
	require.NoError(t, err)

	// Try to promote non-existent version
	err = cli.PromoteVersion("test/secret", "20991231.999")
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "not found")
}

func TestGetSecretWithVersion(t *testing.T) {
	fs := afero.NewMemMapFs()
	stateDir := "/test/state"
	cli := NewCLIInstanceWithStateDir(fs, stateDir)

	// Set up vault with long-term key
	setupTestVault(t, fs, stateDir)

	// Add a secret with multiple versions
	vlt, err := vault.GetCurrentVault(fs, stateDir)
	require.NoError(t, err)

	err = vlt.AddSecret("test/secret", []byte("version-1"), false)
	require.NoError(t, err)

	time.Sleep(10 * time.Millisecond)

	err = vlt.AddSecret("test/secret", []byte("version-2"), true)
	require.NoError(t, err)

	// Get versions
	vaultDir, _ := vlt.GetDirectory()
	secretDir := vaultDir + "/secrets.d/test%secret"
	versions, err := secret.ListVersions(fs, secretDir)
	require.NoError(t, err)
	require.Len(t, versions, 2)

	// Test getting current version (empty version string)
	oldStdout := os.Stdout
	r, w, _ := os.Pipe()
	os.Stdout = w

	err = cli.GetSecretWithVersion("test/secret", "")
	require.NoError(t, err)

	w.Close()
	os.Stdout = oldStdout
	output, _ := io.ReadAll(r)

	assert.Equal(t, "version-2", string(output))

	// Test getting specific version
	r, w, _ = os.Pipe()
	os.Stdout = w

	firstVersion := versions[1] // Older version
	err = cli.GetSecretWithVersion("test/secret", firstVersion)
	require.NoError(t, err)

	w.Close()
	os.Stdout = oldStdout
	output, _ = io.ReadAll(r)

	assert.Equal(t, "version-1", string(output))
}

func TestVersionCommandStructure(t *testing.T) {
	// Test that version commands are properly structured
	cli := NewCLIInstance()
	cmd := VersionCommands(cli)

	assert.Equal(t, "version", cmd.Use)
	assert.Equal(t, "Manage secret versions", cmd.Short)

	// Check subcommands
	listCmd := cmd.Commands()[0]
	assert.Equal(t, "list <secret-name>", listCmd.Use)
	assert.Equal(t, "List all versions of a secret", listCmd.Short)

	promoteCmd := cmd.Commands()[1]
	assert.Equal(t, "promote <secret-name> <version>", promoteCmd.Use)
	assert.Equal(t, "Promote a specific version to current", promoteCmd.Short)
}

func TestListVersionsEmptyOutput(t *testing.T) {
	fs := afero.NewMemMapFs()
	stateDir := "/test/state"
	cli := NewCLIInstanceWithStateDir(fs, stateDir)

	// Set up vault with long-term key
	setupTestVault(t, fs, stateDir)

	// Create a secret directory without versions (edge case)
	vaultDir := stateDir + "/vaults.d/default"
	secretDir := vaultDir + "/secrets.d/test%secret"
	err := fs.MkdirAll(secretDir, 0755)
	require.NoError(t, err)

	// List versions - should show "No versions found"
	err = cli.ListVersions("test/secret")

	// Should succeed even with no versions
	assert.NoError(t, err)
}