package cmd

import (
	"os"
	"runtime"
	"strings"
	"testing"

	crud "github.com/danieljustus/symaira-vault/cmd/crud"
	"github.com/danieljustus/symaira-vault/internal/config"
	vaultpkg "github.com/danieljustus/symaira-vault/internal/vault"
)

func TestSetCommand_HiddenPassword(t *testing.T) {
	vaultDir, passphrase := initVault(t)
	setPassEnv(t, string(passphrase))
	defer setupVaultFlag(t, vaultDir)()

	restore := pipeStdin(t, "myuser\nnew-secret-password\n\n\n")
	defer restore()

	rootCmd.SetArgs([]string{"--vault", vaultDir, "set", "test-entry"})
	defer rootCmd.SetArgs(nil)

	output := captureStdout(func() {
		if err := rootCmd.Execute(); err != nil {
			t.Fatalf("set command failed: %v", err)
		}
	})

	if !strings.Contains(output, "Entry saved") {
		t.Errorf("expected 'Entry saved' in output, got: %s", output)
	}
}

func TestSetCommand_InvalidTOTPSecretRejected(t *testing.T) {
	vaultDir, passphrase := initVault(t)
	setPassEnv(t, string(passphrase))
	defer setupVaultFlag(t, vaultDir)()

	stderr := captureStderr(func() {
		rootCmd.SetArgs([]string{"--vault", vaultDir, "set", "bad-totp-set",
			"--value", "StrongP@ssw0rd123", "--totp-secret", "not-valid-base32!!!"})
		_ = rootCmd.Execute()
		rootCmd.SetArgs(nil)
	})

	if !strings.Contains(stderr, "TOTP secret must be Base32-encoded") {
		t.Errorf("expected TOTP validation error, got: %s", stderr)
	}
	if strings.Contains(stderr, "not-valid-base32!!!") {
		t.Error("error message must not contain the secret value")
	}
}

func TestSetCommand_ValidTOTPSecretAccepted(t *testing.T) {
	vaultDir, passphrase := initVault(t)
	setPassEnv(t, string(passphrase))
	defer setupVaultFlag(t, vaultDir)()

	out := execWithStdout("--vault", vaultDir, "set", "valid-totp-set",
		"--value", "StrongP@ssw0rd123", "--totp-secret", "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ")
	if !strings.Contains(out, "Entry saved") {
		t.Errorf("expected Entry saved, got: %s", out)
	}
}

func TestSetCommand_TOTPSecretWithSpacesAccepted(t *testing.T) {
	vaultDir, passphrase := initVault(t)
	setPassEnv(t, string(passphrase))
	defer setupVaultFlag(t, vaultDir)()

	out := execWithStdout("--vault", vaultDir, "set", "spaced-totp-set",
		"--value", "StrongP@ssw0rd123", "--totp-secret", "GEZD GNBV GY3T QOJQ GEZD GNBV GY3T QOJQ")
	if !strings.Contains(out, "Entry saved") {
		t.Errorf("expected Entry saved, got: %s", out)
	}
}

func TestCmdSet_FieldSyntax(t *testing.T) {
	vaultDir, passphrase := initVault(t)
	setPassEnv(t, string(passphrase))
	defer setupVaultFlag(t, vaultDir)()
	out := execWithStdout("--vault", vaultDir, "set", "myapp.username", "--value", "alice")
	if !strings.Contains(out, "Entry saved") {
		t.Errorf("expected Entry saved, got: %s", out)
	}
}

func TestCmdSet_MergeExisting(t *testing.T) {
	vaultDir, passphrase := initVault(t)
	identity, _ := vaultpkg.OpenWithPassphrase(vaultDir, passphrase)
	entry := &vaultpkg.Entry{Data: map[string]any{"password": "old"}}
	_ = vaultpkg.WriteEntry(vaultDir, "merge-me", entry, identity.Identity)
	setPassEnv(t, string(passphrase))
	defer setupVaultFlag(t, vaultDir)()
	out := execWithStdout("--vault", vaultDir, "set", "merge-me", "--value", "StrongP@ssw0rd123")
	if !strings.Contains(out, "Entry saved") {
		t.Errorf("expected Entry saved, got: %s", out)
	}
}

func TestCmdSet_TOTP(t *testing.T) {
	vaultDir, passphrase := initVault(t)
	setPassEnv(t, string(passphrase))
	defer setupVaultFlag(t, vaultDir)()
	out := execWithStdout("--vault", vaultDir, "set", "totp-set", "--value", "StrongP@ssw0rd123",
		"--totp-secret", "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ", "--totp-issuer", "GitHub")
	if !strings.Contains(out, "Entry saved") {
		t.Errorf("expected Entry saved, got: %s", out)
	}
}

func TestCmdSet_InteractiveField(t *testing.T) {
	vaultDir, passphrase := initVault(t)
	setPassEnv(t, string(passphrase))
	defer setupVaultFlag(t, vaultDir)()
	restore := pipeStdin(t, "newvalue\n")
	defer restore()
	out := captureStdout(func() {
		rootCmd.SetArgs([]string{"--vault", vaultDir, "set", "ifield.custom"})
		_ = rootCmd.Execute()
		rootCmd.SetArgs(nil)
	})
	if !strings.Contains(out, "Entry saved") {
		t.Errorf("expected Entry saved, got: %s", out)
	}
}

func TestCmdSet_InteractiveFieldStdinError(t *testing.T) {
	vaultDir, passphrase := initVault(t)
	setPassEnv(t, string(passphrase))
	defer setupVaultFlag(t, vaultDir)()
	restore := pipeStdin(t, "")
	defer restore()
	stderr := captureStderr(func() {
		rootCmd.SetArgs([]string{"--vault", vaultDir, "set", "ifield.custom"})
		_ = rootCmd.Execute()
		rootCmd.SetArgs(nil)
	})
	if !strings.Contains(stderr, "read value") {
		t.Errorf("expected read value error, got: %s", stderr)
	}
}

func TestCmdSet_Interactive(t *testing.T) {
	vaultDir, passphrase := initVault(t)
	setPassEnv(t, string(passphrase))
	defer setupVaultFlag(t, vaultDir)()
	restore := pipeStdin(t, "alice\nStrongP@ssw0rd123\nhttps://example.com\n\n")
	defer restore()
	out := captureStdout(func() {
		rootCmd.SetArgs([]string{"--vault", vaultDir, "set", "interactive-set"})
		_ = rootCmd.Execute()
		rootCmd.SetArgs(nil)
	})
	if !strings.Contains(out, "Entry saved") {
		t.Errorf("expected Entry saved, got: %s", out)
	}
}

func TestCmdSet_InteractiveStdinError(t *testing.T) {
	vaultDir, passphrase := initVault(t)
	setPassEnv(t, string(passphrase))
	defer setupVaultFlag(t, vaultDir)()
	restore := pipeStdin(t, "")
	defer restore()
	stderr := captureStderr(func() {
		rootCmd.SetArgs([]string{"--vault", vaultDir, "set", "stdin-err"})
		_ = rootCmd.Execute()
		rootCmd.SetArgs(nil)
	})
	if !strings.Contains(stderr, "read username") {
		t.Errorf("expected read username error, got: %s", stderr)
	}
}

func TestCmdSet_Uninitialized(t *testing.T) {
	resetCmdFlags()
	t.Cleanup(resetCmdFlags)
	vaultDir := t.TempDir()
	defer setupVaultFlag(t, vaultDir)()
	stderr := captureStderr(func() {
		rootCmd.SetArgs([]string{"--vault", vaultDir, "set", "x", "--value", "v"})
		_ = rootCmd.Execute()
		rootCmd.SetArgs(nil)
	})
	if !strings.Contains(stderr, "vault not initialized") && !strings.Contains(stderr, "Error") {
		t.Errorf("expected vault not initialized, got: %s", stderr)
	}
}

func TestCmdSet_InteractiveFull(t *testing.T) {
	vaultDir, passphrase := initVault(t)
	setPassEnv(t, string(passphrase))
	defer setupVaultFlag(t, vaultDir)()
	restore := pipeStdin(t, "alice\nStrongP@ssw0rd123\nhttps://example.com\n\n")
	defer restore()
	out := captureStdout(func() {
		rootCmd.SetArgs([]string{"--vault", vaultDir, "set", "interactive-full"})
		_ = rootCmd.Execute()
		rootCmd.SetArgs(nil)
	})
	if !strings.Contains(out, "Entry saved") {
		t.Errorf("expected Entry saved, got: %s", out)
	}
	identity, _ := vaultpkg.OpenWithPassphrase(vaultDir, passphrase)
	entry, err := vaultpkg.ReadEntry(vaultDir, "interactive-full", identity.Identity)
	if err != nil {
		t.Fatalf("read entry: %v", err)
	}
	if entry.Data["username"] != "alice" {
		t.Errorf("expected username alice, got: %v", entry.Data["username"])
	}
	if entry.Data["password"] != "StrongP@ssw0rd123" {
		t.Errorf("expected password StrongP@ssw0rd123, got: %v", entry.Data["password"])
	}
	if entry.Data["url"] != "https://example.com" {
		t.Errorf("expected url https://example.com, got: %v", entry.Data["url"])
	}
}

func TestCmdSet_InteractiveFieldWithTOTP(t *testing.T) {
	vaultDir, passphrase := initVault(t)
	setPassEnv(t, string(passphrase))
	defer setupVaultFlag(t, vaultDir)()
	restore := pipeStdin(t, "fieldvalue\n")
	defer restore()
	out := captureStdout(func() {
		rootCmd.SetArgs([]string{"--vault", vaultDir, "set", "totpfield.custom",
			"--totp-secret", "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ", "--totp-issuer", "MyService"})
		_ = rootCmd.Execute()
		rootCmd.SetArgs(nil)
	})
	if !strings.Contains(out, "Entry saved") {
		t.Errorf("expected Entry saved, got: %s", out)
	}
	identity, _ := vaultpkg.OpenWithPassphrase(vaultDir, passphrase)
	entry, err := vaultpkg.ReadEntry(vaultDir, "totpfield", identity.Identity)
	if err != nil {
		t.Fatalf("read entry: %v", err)
	}
	if entry.Data["custom"] != "fieldvalue" {
		t.Errorf("expected custom fieldvalue, got: %v", entry.Data["custom"])
	}
	totp, ok := entry.Data["totp"].(map[string]any)
	if !ok {
		t.Fatal("expected totp data in entry")
	}
	if totp["secret"] != "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ" {
		t.Errorf("expected totp secret, got: %v", totp["secret"])
	}
}

func TestCmdSet_AutoCommitError(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("skipping on windows: stderr capture not reliable")
	}
	// Test skipped: set command uses slog for auto-commit failure warnings
	// instead of writing to stderr.
	t.Skip("skipped: auto-commit warning is logged, not written to stderr")
	vaultDir, passphrase := initVault(t)
	setupGitWithBrokenObjects(t, vaultDir)
	setPassEnv(t, string(passphrase))
	defer setupVaultFlag(t, vaultDir)()

	stderr := captureStderr(func() {
		rootCmd.SetArgs([]string{"--vault", vaultDir, "set", "autocommit-set", "--value", "StrongP@ssw0rd123"})
		_ = rootCmd.Execute()
		rootCmd.SetArgs(nil)
	})
	if !strings.Contains(stderr, "auto-commit failed") {
		t.Errorf("expected auto-commit warning in stderr: %s", stderr)
	}
}

func TestSet_ErrorPaths(t *testing.T) {
	resetVaultState(t)
	t.Run("uninitialized vault", func(t *testing.T) {
		tmpDir := t.TempDir()
		_ = os.Setenv("OPENPASS_VAULT", tmpDir)
		defer func() { _ = os.Unsetenv("OPENPASS_VAULT") }()

		rootCmd.SetArgs([]string{"--vault", tmpDir, "set", "test.key", "--value", "val"})
		defer rootCmd.SetArgs(nil)

		err := rootCmd.Execute()
		if err == nil || !strings.Contains(err.Error(), "not initialized") {
			t.Errorf("expected 'not initialized' error, got: %v", err)
		}
	})
}

func TestSet_InteractiveMode(t *testing.T) {
	resetVaultState(t)

	tmpDir := t.TempDir()
	_ = os.Setenv("OPENPASS_VAULT", tmpDir)
	_ = os.Setenv("OPENPASS_PASSPHRASE", "test")
	defer func() {
		_ = os.Unsetenv("OPENPASS_VAULT")
		_ = os.Unsetenv("OPENPASS_PASSPHRASE")
	}()

	cfg := config.Default()
	_, _ = vaultpkg.InitWithPassphrase(tmpDir, []byte("test"), cfg)

	crud.SetValue = ""
	crud.SetTOTPSecret = ""
	crud.SetTOTPIssuer = ""
	crud.SetTOTPAccount = ""

	oldStdin := os.Stdin
	r, w, _ := os.Pipe()
	os.Stdin = r

	go func() {
		_, _ = w.WriteString("myuser\n")
		_, _ = w.WriteString("StrongP@ssw0rd123\n")
		_, _ = w.WriteString("https://example.com\n")
		_, _ = w.WriteString("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ\n")
		_ = w.Close()
	}()

	rootCmd.SetArgs([]string{"--vault", tmpDir, "set", "interactive-set"})
	defer rootCmd.SetArgs(nil)

	output := captureStdout(func() {
		_ = rootCmd.Execute()
	})

	os.Stdin = oldStdin
	_ = r.Close()

	if !strings.Contains(output, "Entry saved") {
		t.Errorf("expected 'Entry saved', got: %s", output)
	}
}

func TestSet_InteractiveMode_Field(t *testing.T) {
	resetVaultState(t)

	tmpDir := t.TempDir()
	_ = os.Setenv("OPENPASS_VAULT", tmpDir)
	_ = os.Setenv("OPENPASS_PASSPHRASE", "test")
	defer func() {
		_ = os.Unsetenv("OPENPASS_VAULT")
		_ = os.Unsetenv("OPENPASS_PASSPHRASE")
	}()

	cfg := config.Default()
	identity, _ := vaultpkg.InitWithPassphrase(tmpDir, []byte("test"), cfg)
	_ = vaultpkg.WriteEntry(tmpDir, "test", &vaultpkg.Entry{Data: map[string]any{"password": "secret"}}, identity)

	crud.SetValue = ""

	oldStdin := os.Stdin
	r, w, _ := os.Pipe()
	os.Stdin = r

	go func() {
		_, _ = w.WriteString("fieldvalue\n")
		_ = w.Close()
	}()

	rootCmd.SetArgs([]string{"--vault", tmpDir, "set", "test.customfield"})
	defer rootCmd.SetArgs(nil)

	output := captureStdout(func() {
		_ = rootCmd.Execute()
	})

	os.Stdin = oldStdin
	_ = r.Close()

	if !strings.Contains(output, "Entry saved") {
		t.Errorf("expected 'Entry saved', got: %s", output)
	}
}

func TestSet_InteractiveReadErrors(t *testing.T) {
	resetVaultState(t)

	tests := []struct {
		name string
		path string
		err  string
	}{
		{"field value EOF", "test.field", "read value"},
		{"username EOF", "test", "read username"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpDir := t.TempDir()
			_ = os.Setenv("OPENPASS_VAULT", tmpDir)
			_ = os.Setenv("OPENPASS_PASSPHRASE", "test")
			defer func() {
				_ = os.Unsetenv("OPENPASS_VAULT")
				_ = os.Unsetenv("OPENPASS_PASSPHRASE")
			}()

			cfg := config.Default()
			_, _ = vaultpkg.InitWithPassphrase(tmpDir, []byte("test"), cfg)

			crud.SetValue = ""
			crud.SetTOTPSecret = ""

			oldStdin := os.Stdin
			r, w, _ := os.Pipe()
			os.Stdin = r
			_ = w.Close()

			rootCmd.SetArgs([]string{"--vault", tmpDir, "set", tt.path})
			defer rootCmd.SetArgs(nil)

			err := rootCmd.Execute()
			os.Stdin = oldStdin
			_ = r.Close()

			if err == nil || !strings.Contains(err.Error(), tt.err) {
				t.Errorf("expected %q error, got: %v", tt.err, err)
			}
		})
	}
}
