package restic

import (
	"errors"
	"testing"

	"git.secluded.site/keld/internal/config"
)

func TestBuildSnapshotCmd(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		cfg      *config.ResolvedConfig
		wantArgv []string
		wantErr  bool
	}{
		{
			name: "basic repo flag",
			cfg: &config.ResolvedConfig{
				Command: "restore",
				Flags: []config.Flag{
					{Name: "--repo", Value: "/srv/backup"},
					{Name: "--target", Value: "/tmp/restore"},
				},
			},
			wantArgv: []string{"restic", "snapshots", "--json", "--repo", "/srv/backup"},
		},
		{
			name: "repo with password-file",
			cfg: &config.ResolvedConfig{
				Command: "restore",
				Flags: []config.Flag{
					{Name: "--repo", Value: "rclone:remote:/backup"},
					{Name: "--password-file", Value: "/etc/restic/pw"},
					{Name: "--target", Value: "/tmp/restore"},
					{Name: "--overwrite", Value: "if-changed"},
				},
			},
			wantArgv: []string{
				"restic", "snapshots", "--json",
				"--repo", "rclone:remote:/backup",
				"--password-file", "/etc/restic/pw",
			},
		},
		{
			name: "repo with cache-dir and no-lock",
			cfg: &config.ResolvedConfig{
				Command: "restore",
				Flags: []config.Flag{
					{Name: "--repo", Value: "/srv/backup"},
					{Name: "--cache-dir", Value: "/tmp/cache"},
					{Name: "--no-lock"},
					{Name: "--target", Value: "/tmp/restore"},
				},
			},
			wantArgv: []string{
				"restic", "snapshots", "--json",
				"--repo", "/srv/backup",
				"--cache-dir", "/tmp/cache",
				"--no-lock",
			},
		},
		{
			name: "repo via repository-file",
			cfg: &config.ResolvedConfig{
				Command: "restore",
				Flags: []config.Flag{
					{Name: "--repository-file", Value: "/etc/restic/repo"},
					{Name: "--target", Value: "/tmp/restore"},
				},
			},
			wantArgv: []string{
				"restic", "snapshots", "--json",
				"--repository-file", "/etc/restic/repo",
			},
		},
		{
			name: "multiple connection flags preserved",
			cfg: &config.ResolvedConfig{
				Command: "restore",
				Flags: []config.Flag{
					{Name: "--repo", Value: "s3:https://bucket/backup"},
					{Name: "--cacert", Value: "/etc/ssl/custom.pem"},
					{Name: "--insecure-tls"},
					{Name: "--limit-download", Value: "1024"},
					{Name: "--tls-client-cert", Value: "/etc/ssl/client.pem"},
					{Name: "--key-hint", Value: "abc123"},
					{Name: "--target", Value: "/tmp/restore"},
					{Name: "--exclude", Value: "*.log"},
				},
			},
			wantArgv: []string{
				"restic", "snapshots", "--json",
				"--repo", "s3:https://bucket/backup",
				"--cacert", "/etc/ssl/custom.pem",
				"--insecure-tls",
				"--limit-download", "1024",
				"--tls-client-cert", "/etc/ssl/client.pem",
				"--key-hint", "abc123",
			},
		},
		{
			name: "repo from environ only",
			cfg: &config.ResolvedConfig{
				Command: "restore",
				Flags: []config.Flag{
					{Name: "--target", Value: "/tmp/restore"},
				},
				Environ: map[string]string{
					"RESTIC_REPOSITORY": "/srv/backup",
				},
			},
			// No --repo flag needed; restic reads the env var directly.
			wantArgv: []string{"restic", "snapshots", "--json"},
		},
		{
			name: "password-command flag preserved",
			cfg: &config.ResolvedConfig{
				Command: "restore",
				Flags: []config.Flag{
					{Name: "--repo", Value: "/srv/backup"},
					{Name: "--password-command", Value: "op read 'op://Vault/Backup/password'"},
					{Name: "--target", Value: "/tmp/restore"},
				},
			},
			wantArgv: []string{
				"restic", "snapshots", "--json",
				"--repo", "/srv/backup",
				"--password-command", "op read 'op://Vault/Backup/password'",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			argv, err := buildSnapshotCmd(tt.cfg)
			if (err != nil) != tt.wantErr {
				t.Fatalf("buildSnapshotCmd(): err=%v, wantErr=%v", err, tt.wantErr)
			}
			if err != nil {
				return
			}

			if len(argv) != len(tt.wantArgv) {
				t.Fatalf("argv length: got %d %v, want %d %v",
					len(argv), argv, len(tt.wantArgv), tt.wantArgv)
			}
			for i := range argv {
				if argv[i] != tt.wantArgv[i] {
					t.Errorf("argv[%d]: got %q, want %q", i, argv[i], tt.wantArgv[i])
				}
			}
		})
	}
}

func TestBuildSnapshotCmdNoRepoSentinel(t *testing.T) {
	// Not parallel: uses t.Setenv to control process environment.
	t.Setenv("RESTIC_REPOSITORY", "")
	t.Setenv("RESTIC_REPOSITORY_FILE", "")

	cfg := &config.ResolvedConfig{
		Command: "restore",
		Flags: []config.Flag{
			{Name: "--target", Value: "/tmp/restore"},
		},
	}

	_, err := buildSnapshotCmd(cfg)
	if !errors.Is(err, ErrNoRepo) {
		t.Errorf("expected ErrNoRepo, got %v", err)
	}
}

func TestBuildSnapshotCmdProcessEnv(t *testing.T) {
	// Not parallel: uses t.Setenv to control process environment.
	t.Setenv("RESTIC_REPOSITORY", "/srv/from-env")
	t.Setenv("RESTIC_REPOSITORY_FILE", "")

	cfg := &config.ResolvedConfig{
		Command: "restore",
		Flags: []config.Flag{
			{Name: "--target", Value: "/tmp/restore"},
		},
	}

	argv, err := buildSnapshotCmd(cfg)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// No --repo flag in argv since repo comes from process env;
	// restic reads it directly.
	wantArgv := []string{"restic", "snapshots", "--json"}
	if len(argv) != len(wantArgv) {
		t.Fatalf("argv: got %v, want %v", argv, wantArgv)
	}
	for i := range argv {
		if argv[i] != wantArgv[i] {
			t.Errorf("argv[%d]: got %q, want %q", i, argv[i], wantArgv[i])
		}
	}
}

func TestCopyEnviron(t *testing.T) {
	t.Parallel()

	original := map[string]string{
		"RESTIC_REPOSITORY":         "/srv/backup",
		"RESTIC_PASSWORD_COMMAND":   "op read 'op://Vault/pw'",
		"AWS_ACCESS_KEY_ID_COMMAND": "op read 'op://Vault/aws-key'",
	}

	copied := copyEnviron(original)

	// Mutation of the copy must not affect the original.
	copied["RESTIC_REPOSITORY"] = "changed"
	delete(copied, "RESTIC_PASSWORD_COMMAND")
	copied["NEW_KEY"] = "new_value"

	if original["RESTIC_REPOSITORY"] != "/srv/backup" {
		t.Error("original RESTIC_REPOSITORY was mutated")
	}
	if _, ok := original["RESTIC_PASSWORD_COMMAND"]; !ok {
		t.Error("original RESTIC_PASSWORD_COMMAND was deleted")
	}
	if _, ok := original["NEW_KEY"]; ok {
		t.Error("original gained NEW_KEY from copy")
	}
}
