package restic

import (
	"strings"
	"testing"
)

// realistic restic ls --json output sample. First line is the
// snapshot preamble, subsequent lines are nodes. Trimmed to a few
// entries that exercise depth filtering.
const sampleLsOutput = `{"struct_type":"snapshot","time":"2026-05-04T09:14:00Z","id":"f3a7b2c1"}
{"name":"etc","type":"dir","path":"/etc","permissions":"drwxr-xr-x","struct_type":"node"}
{"name":"nginx","type":"dir","path":"/etc/nginx","permissions":"drwxr-xr-x","struct_type":"node"}
{"name":"nginx.conf","type":"file","path":"/etc/nginx/nginx.conf","size":2400,"struct_type":"node"}
{"name":"sites-available","type":"dir","path":"/etc/nginx/sites-available","struct_type":"node"}
{"name":"alfa.conf","type":"file","path":"/etc/nginx/sites-available/alfa.conf","size":3100,"struct_type":"node"}
{"name":"default.conf","type":"file","path":"/etc/nginx/sites-available/default.conf","size":2900,"struct_type":"node"}
`

func TestParseLsChildrenAtRoot(t *testing.T) {
	t.Parallel()
	entries, err := parseLsChildren(strings.NewReader(sampleLsOutput), "/")
	if err != nil {
		t.Fatalf("parse: %v", err)
	}
	if len(entries) != 1 {
		t.Fatalf("entries: got %d (%+v), want 1", len(entries), entries)
	}
	if entries[0].Name != "etc" || entries[0].Path != "/etc" || entries[0].Type != "dir" {
		t.Fatalf("entry: %+v", entries[0])
	}
}

func TestParseLsChildrenAtEtc(t *testing.T) {
	t.Parallel()
	entries, err := parseLsChildren(strings.NewReader(sampleLsOutput), "/etc")
	if err != nil {
		t.Fatalf("parse: %v", err)
	}
	if len(entries) != 1 {
		t.Fatalf("entries: got %d, want 1 (just nginx, not nested children)", len(entries))
	}
	if entries[0].Name != "nginx" {
		t.Fatalf("entry: %+v", entries[0])
	}
}

func TestParseLsChildrenAtNginx(t *testing.T) {
	t.Parallel()
	entries, err := parseLsChildren(strings.NewReader(sampleLsOutput), "/etc/nginx")
	if err != nil {
		t.Fatalf("parse: %v", err)
	}
	if len(entries) != 2 {
		t.Fatalf("entries: got %d (%+v), want 2 (nginx.conf + sites-available, not nested)",
			len(entries), entries)
	}
	gotNames := []string{entries[0].Name, entries[1].Name}
	want := map[string]bool{"nginx.conf": true, "sites-available": true}
	for _, n := range gotNames {
		if !want[n] {
			t.Errorf("unexpected name %q in result", n)
		}
	}
}

func TestParseLsChildrenAtSitesAvailable(t *testing.T) {
	t.Parallel()
	entries, err := parseLsChildren(strings.NewReader(sampleLsOutput), "/etc/nginx/sites-available")
	if err != nil {
		t.Fatalf("parse: %v", err)
	}
	if len(entries) != 2 {
		t.Fatalf("entries: got %d, want 2", len(entries))
	}
	for _, e := range entries {
		if e.Type != "file" {
			t.Errorf("expected file type, got %q on %q", e.Type, e.Name)
		}
	}
}

func TestNormalizeTreePath(t *testing.T) {
	t.Parallel()
	cases := []struct{ in, want string }{
		{"", "/"},
		{"/", "/"},
		{"/etc", "/etc"},
		{"/etc/", "/etc"},
		{"etc/nginx", "/etc/nginx"},
		{"/etc//nginx", "/etc/nginx"},
		{"/etc/./nginx", "/etc/nginx"},
	}
	for _, c := range cases {
		got := normalizeTreePath(c.in)
		if got != c.want {
			t.Errorf("normalizeTreePath(%q): got %q, want %q", c.in, got, c.want)
		}
	}
}

func TestIsDirectChild(t *testing.T) {
	t.Parallel()
	cases := []struct {
		child, parent string
		want          bool
	}{
		{"/etc", "/", true},
		{"/etc/nginx", "/", false},
		{"/etc/nginx", "/etc", true},
		{"/etc/nginx/conf", "/etc", false},
		{"/etc/nginx/conf", "/etc/nginx", true},
		{"/etc", "/etc", false},
		{"/etcc", "/etc", false}, // prefix match guard
	}
	for _, c := range cases {
		got := isDirectChild(c.child, c.parent)
		if got != c.want {
			t.Errorf("isDirectChild(%q, %q): got %v, want %v",
				c.child, c.parent, got, c.want)
		}
	}
}
