package zddc

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestResolveVirtualView_Roots(t *testing.T) {
	root := t.TempDir()
	cases := []struct {
		url  string
		want VirtualViewKind
	}{
		{"/Project/ssr", VirtualViewSSRRoot},
		{"/Project/ssr/", VirtualViewSSRRoot},
		{"/Project/mdl", VirtualViewMDLRoot},
		{"/Project/mdl/", VirtualViewMDLRoot},
		{"/Project/rsk", VirtualViewRSKRoot},
		{"/Project/rsk/", VirtualViewRSKRoot},
	}
	for _, tc := range cases {
		got := ResolveVirtualView(root, tc.url)
		if !got.Resolved || got.Kind != tc.want {
			t.Errorf("%s: kind=%d resolved=%v; want kind=%d resolved=true", tc.url, got.Kind, got.Resolved, tc.want)
		}
		if got.Project != "Project" {
			t.Errorf("%s: project=%q want Project", tc.url, got.Project)
		}
		if !got.Kind.IsRootKind() {
			t.Errorf("%s: IsRootKind=false", tc.url)
		}
	}
}

func TestResolveVirtualView_Specs(t *testing.T) {
	root := t.TempDir()
	cases := []struct {
		url      string
		wantKind VirtualViewKind
		wantBase string
	}{
		{"/Project/ssr/table.yaml", VirtualViewSSRSpec, "table.yaml"},
		{"/Project/ssr/form.yaml", VirtualViewSSRSpec, "form.yaml"},
		{"/Project/mdl/table.yaml", VirtualViewMDLSpec, "table.yaml"},
		{"/Project/mdl/form.yaml", VirtualViewMDLSpec, "form.yaml"},
		{"/Project/rsk/table.yaml", VirtualViewRSKSpec, "table.yaml"},
		{"/Project/rsk/form.yaml", VirtualViewRSKSpec, "form.yaml"},
	}
	for _, tc := range cases {
		got := ResolveVirtualView(root, tc.url)
		if !got.Resolved || got.Kind != tc.wantKind {
			t.Errorf("%s: kind=%d resolved=%v; want kind=%d", tc.url, got.Kind, got.Resolved, tc.wantKind)
		}
		if got.SpecBase != tc.wantBase {
			t.Errorf("%s: SpecBase=%q want %q", tc.url, got.SpecBase, tc.wantBase)
		}
		if !got.Kind.IsSpecKind() {
			t.Errorf("%s: IsSpecKind=false", tc.url)
		}
	}
}

func TestResolveVirtualView_SSRRow(t *testing.T) {
	root := t.TempDir()
	got := ResolveVirtualView(root, "/Project/ssr/0330C1.yaml")
	if !got.Resolved || got.Kind != VirtualViewSSRRow {
		t.Fatalf("unexpected resolution: %+v", got)
	}
	if got.Party != "0330C1" {
		t.Errorf("Party=%q want 0330C1", got.Party)
	}
	wantAbs := filepath.Join(root, "Project", "archive", "0330C1", "ssr.yaml")
	if got.CanonicalAbs != wantAbs {
		t.Errorf("CanonicalAbs=%q want %q", got.CanonicalAbs, wantAbs)
	}
	wantSchema := filepath.Join(root, "Project", "archive", "0330C1", "ssr.form.yaml")
	if got.SchemaAbs != wantSchema {
		t.Errorf("SchemaAbs=%q want %q", got.SchemaAbs, wantSchema)
	}
	if !got.Kind.IsRowKind() {
		t.Errorf("IsRowKind=false")
	}
}

func TestResolveVirtualView_RollupRow(t *testing.T) {
	root := t.TempDir()
	cases := []struct {
		url          string
		wantKind     VirtualViewKind
		wantParty    string
		wantFilename string
		wantSlot     string
	}{
		{"/Project/mdl/0330C1__D-001.yaml", VirtualViewMDLRow, "0330C1", "D-001.yaml", "mdl"},
		{"/Project/rsk/Acme__R-005.yaml", VirtualViewRSKRow, "Acme", "R-005.yaml", "rsk"},
	}
	for _, tc := range cases {
		got := ResolveVirtualView(root, tc.url)
		if !got.Resolved || got.Kind != tc.wantKind {
			t.Errorf("%s: kind=%d resolved=%v; want kind=%d", tc.url, got.Kind, got.Resolved, tc.wantKind)
			continue
		}
		if got.Party != tc.wantParty {
			t.Errorf("%s: Party=%q want %q", tc.url, got.Party, tc.wantParty)
		}
		if got.RowFilename != tc.wantFilename {
			t.Errorf("%s: RowFilename=%q want %q", tc.url, got.RowFilename, tc.wantFilename)
		}
		wantAbs := filepath.Join(root, "Project", "archive", tc.wantParty, tc.wantSlot, tc.wantFilename)
		if got.CanonicalAbs != wantAbs {
			t.Errorf("%s: CanonicalAbs=%q want %q", tc.url, got.CanonicalAbs, wantAbs)
		}
	}
}

func TestResolveVirtualView_NonMatches(t *testing.T) {
	root := t.TempDir()
	cases := []string{
		"/",
		"/Project",
		"/Project/",
		"/Project/working",
		"/Project/archive/Acme/mdl",
		"/Project/ssr/invalid__name__double.yaml", // double-double underscore is rejected
		"/Project/mdl/__leading.yaml",             // empty party
		"/Project/mdl/party__.yaml",                // empty rowBase
		"/Project/ssr/.hidden.yaml",                // dotfile party name
		"/Project/ssr/0330C1.yaml/sub",             // sub-path under row file
		"/Project/notaslot/table.yaml",
	}
	for _, url := range cases {
		got := ResolveVirtualView(root, url)
		if got.Resolved {
			t.Errorf("%s: unexpectedly resolved as kind %d", url, got.Kind)
		}
	}
}

func TestIsSSRCreateURL(t *testing.T) {
	cases := []struct {
		url     string
		want    string
		wantOK  bool
	}{
		{"/Project/ssr/form.html", "Project", true},
		{"/Other-Project/ssr/form.html", "Other-Project", true},
		{"/Project/ssr/", "", false},
		{"/Project/ssr/Acme.yaml.html", "", false},
		{"/Project/mdl/form.html", "", false},
		{"/.hidden/ssr/form.html", "", false},
	}
	for _, tc := range cases {
		got, ok := IsSSRCreateURL(tc.url)
		if ok != tc.wantOK {
			t.Errorf("%s: ok=%v want %v", tc.url, ok, tc.wantOK)
		}
		if got != tc.want {
			t.Errorf("%s: project=%q want %q", tc.url, got, tc.want)
		}
	}
}

func TestStripYAMLHTML(t *testing.T) {
	cases := []struct {
		in     string
		want   string
		wantOK bool
	}{
		{"/Project/ssr/Acme.yaml.html", "/Project/ssr/Acme.yaml", true},
		{"/Project/mdl/foo__bar.yaml.html", "/Project/mdl/foo__bar.yaml", true},
		{"/Project/ssr/Acme.yaml", "/Project/ssr/Acme.yaml", false},
		{"/Project/ssr/form.html", "/Project/ssr/form.html", false},
	}
	for _, tc := range cases {
		got, ok := StripYAMLHTML(tc.in)
		if got != tc.want || ok != tc.wantOK {
			t.Errorf("%s: (%q, %v) want (%q, %v)", tc.in, got, ok, tc.want, tc.wantOK)
		}
	}
}

func TestValidPartyName(t *testing.T) {
	ok := []string{"0330C1", "Acme", "Acme.Inc", "Acme-Subsidiary", "a", "0", "0330C1.draft", "X-Y-Z"}
	bad := []string{"", ".hidden", "_underscore", "Acme/sub", "Acme Inc", "Acme(Inc)", "Acme,Inc", "..", "-leading-dash"}
	for _, s := range ok {
		if !ValidPartyName(s) {
			t.Errorf("ValidPartyName(%q) = false, want true", s)
		}
	}
	for _, s := range bad {
		if ValidPartyName(s) {
			t.Errorf("ValidPartyName(%q) = true, want false", s)
		}
	}
}

func TestListSSRParties(t *testing.T) {
	root := t.TempDir()
	projectAbs := filepath.Join(root, "Project")
	for _, party := range []string{"0330C1", "0440P2", "Acme"} {
		if err := os.MkdirAll(filepath.Join(projectAbs, "archive", party), 0o755); err != nil {
			t.Fatal(err)
		}
	}
	// A file (not a dir) and a hidden folder should be filtered out.
	if err := os.WriteFile(filepath.Join(projectAbs, "archive", "stray.txt"), []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.MkdirAll(filepath.Join(projectAbs, "archive", ".hidden"), 0o755); err != nil {
		t.Fatal(err)
	}

	parties, err := ListSSRParties(root, projectAbs)
	if err != nil {
		t.Fatal(err)
	}
	want := []string{"0330C1", "0440P2", "Acme"}
	if strings.Join(parties, ",") != strings.Join(want, ",") {
		t.Errorf("got %v, want %v", parties, want)
	}
}

func TestListSSRParties_NoArchive(t *testing.T) {
	root := t.TempDir()
	projectAbs := filepath.Join(root, "Project")
	parties, err := ListSSRParties(root, projectAbs)
	if err != nil {
		t.Fatalf("err=%v want nil", err)
	}
	if len(parties) != 0 {
		t.Errorf("got %v, want empty", parties)
	}
}

func TestListRollupRows(t *testing.T) {
	root := t.TempDir()
	projectAbs := filepath.Join(root, "Project")

	for _, party := range []string{"0330C1", "0440P2"} {
		mdlDir := filepath.Join(projectAbs, "archive", party, "mdl")
		if err := os.MkdirAll(mdlDir, 0o755); err != nil {
			t.Fatal(err)
		}
	}
	// Real rows.
	if err := os.WriteFile(filepath.Join(projectAbs, "archive", "0330C1", "mdl", "D-001.yaml"), []byte("id: D-001\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(projectAbs, "archive", "0330C1", "mdl", "D-002.yaml"), []byte("id: D-002\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(projectAbs, "archive", "0440P2", "mdl", "D-010.yaml"), []byte("id: D-010\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	// Skipped: table.yaml, form.yaml, anything containing "__".
	if err := os.WriteFile(filepath.Join(projectAbs, "archive", "0330C1", "mdl", "table.yaml"), []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(projectAbs, "archive", "0330C1", "mdl", "form.yaml"), []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(projectAbs, "archive", "0330C1", "mdl", "weird__name.yaml"), []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}

	rows, err := ListRollupRows(root, projectAbs, "mdl")
	if err != nil {
		t.Fatal(err)
	}
	if len(rows) != 3 {
		t.Fatalf("got %d rows, want 3; rows=%+v", len(rows), rows)
	}
	wantNames := []string{"0330C1__D-001.yaml", "0330C1__D-002.yaml", "0440P2__D-010.yaml"}
	for i, want := range wantNames {
		if rows[i].SyntheticName != want {
			t.Errorf("row[%d].SyntheticName=%q want %q", i, rows[i].SyntheticName, want)
		}
	}
}

func TestListRollupRows_BadSlot(t *testing.T) {
	root := t.TempDir()
	if _, err := ListRollupRows(root, root, "ssr"); err == nil {
		t.Error("expected error for slot=ssr (only mdl/rsk valid)")
	}
}
