package zddc

import (
	"testing"
)

func TestValidatePattern(t *testing.T) {
	cases := []struct {
		pattern string
		ok      bool
	}{
		{"alice@example.com", true},
		{"*@example.com", true},
		{"alice@*", true},
		{"*", true},
		{"", false},
		{" alice@example.com", false},
		{"alice@example.com ", false},
		{"alice @example.com", false},
		{"alice@ex ample.com", false},
		{"alice@@example.com", false},
		{"@example.com", false},
		{"alice@", false},
		{"@", false},
	}
	for _, tc := range cases {
		t.Run(tc.pattern, func(t *testing.T) {
			err := ValidatePattern(tc.pattern)
			if tc.ok && err != nil {
				t.Errorf("ValidatePattern(%q) = %v, want nil", tc.pattern, err)
			}
			if !tc.ok && err == nil {
				t.Errorf("ValidatePattern(%q) = nil, want error", tc.pattern)
			}
		})
	}
}

func TestValidateFile(t *testing.T) {
	zf := ZddcFile{
		Title:  "ok",
		ACL:    ACLRules{Allow: []string{"good@example.com", "@bad"}, Deny: []string{"two@@ats"}},
		Admins: []string{"@nobody"},
	}
	errs := ValidateFile(zf)
	// expect 3 errors
	if len(errs) != 3 {
		t.Fatalf("got %d errors, want 3: %+v", len(errs), errs)
	}
	wantFields := map[string]bool{
		"acl.allow[1]": false,
		"acl.deny[0]":  false,
		"admins[0]":    false,
	}
	for _, e := range errs {
		if _, ok := wantFields[e.Field]; !ok {
			t.Errorf("unexpected error field: %q", e.Field)
			continue
		}
		wantFields[e.Field] = true
	}
	for f, seen := range wantFields {
		if !seen {
			t.Errorf("missing error for field %q", f)
		}
	}
}

func TestValidateAppSourceSpec(t *testing.T) {
	cases := []struct {
		spec string
		ok   bool
	}{
		// Channel shorthand (with and without leading colon)
		{"stable", true},
		{"beta", true},
		{"alpha", true},
		{":stable", true},
		{":beta", true},
		{":alpha", true},
		// Version pin shorthand (full, partial, with/without leading 'v')
		{"v0.0.4", true},
		{"0.0.4", true},
		{"v0.0", true},
		{"0.0", true},
		{"v0", true},
		{"0", true},
		{"v1.2.3", true},
		{":v0.0.4", true},
		{":0.0.4", true},
		// URLs
		{"https://zddc.varasys.io/releases/archive_stable.html", true},
		{"http://my-fork.example.com/archive.html", true},
		{"https://my-mirror.example/releases", true},                  // URL-prefix only
		{"https://my-mirror.example/releases:stable", true},           // URL-prefix + channel
		{"https://my-mirror.example/releases:v0.0.4", true},           // URL-prefix + version
		{"https://my-mirror.example:8080/releases", true},             // URL with port
		{"https://my-mirror.example:8080/releases:stable", true},      // URL with port + channel
		// Paths
		{"/abs/path.html", true},
		{"./local.html", true},
		{"../sibling.html", true},
		// Errors
		{"", false},
		{"  stable", false},
		{"stable ", false},
		{"with space", false},
		{"https://", false},
		{"https://host/path/file.html:stable", false}, // .html URL with suffix
		{"random-thing", false},
		{":", false},
		{":random", false},
		{"v", false},
		{"v0.", false},
		{".0.0", false},
		{"v0.0.0.0", false},
		{"v0.a.0", false},
		{"https://my-mirror.example/releases:bogus", false}, // bad channel suffix
	}
	for _, tc := range cases {
		t.Run(tc.spec, func(t *testing.T) {
			err := ValidateAppSourceSpec(tc.spec)
			if tc.ok && err != nil {
				t.Errorf("ValidateAppSourceSpec(%q) = %v, want nil", tc.spec, err)
			}
			if !tc.ok && err == nil {
				t.Errorf("ValidateAppSourceSpec(%q) = nil, want error", tc.spec)
			}
		})
	}
}

func TestIsValidAppsKey(t *testing.T) {
	cases := []struct {
		key string
		ok  bool
	}{
		{"default", true},
		{"archive", true},
		{"transmittal", true},
		{"classifier", true},
		{"mdedit", true},
		{"landing", true},
		{"unknown", false},
		{"", false},
		{"DEFAULT", false}, // case-sensitive
	}
	for _, tc := range cases {
		t.Run(tc.key, func(t *testing.T) {
			if got := IsValidAppsKey(tc.key); got != tc.ok {
				t.Errorf("IsValidAppsKey(%q) = %v, want %v", tc.key, got, tc.ok)
			}
		})
	}
}

func TestValidateFile_Apps(t *testing.T) {
	zf := ZddcFile{
		Apps: map[string]string{
			"archive":    "stable",                                             // ok
			"classifier": "v0.0.4",                                             // ok
			"default":    "https://zddc.varasys.io/releases:stable",            // ok (default key + URL+channel)
			"transmittal": ":beta",                                              // ok (channel-only)
			"mdedit":     "https://my-mirror.example/releases",                 // ok (URL-prefix only)
			"unknown":    "stable",                                             // unknown app
			"landing":    "what is this",                                       // bad spec
		},
	}
	errs := ValidateFile(zf)
	want := map[string]bool{
		"apps.unknown": false,
		"apps.landing": false,
	}
	for _, e := range errs {
		if _, ok := want[e.Field]; ok {
			want[e.Field] = true
		} else {
			t.Errorf("unexpected error field: %q (%s)", e.Field, e.Message)
		}
	}
	for f, seen := range want {
		if !seen {
			t.Errorf("missing error for field %q (got: %+v)", f, errs)
		}
	}
}

func TestValidateProjectName(t *testing.T) {
	cases := []struct {
		name string
		ok   bool
	}{
		{"alpha", true},
		{"Alpha", true},
		{"a", true},
		{"a1", true},
		{"a-1", true},
		{"a_b", true},
		{"123-project", true},
		{"Site-3", true},
		{"", false},
		{".hidden", false},
		{"_template", false},
		{"-leading-dash", false},
		{"foo bar", false},
		{"foo/bar", false},
		{"foo\\bar", false},
		{"foo.bar", false},
		{"..", false},
		{".", false},
		{string(make([]byte, 65)), false},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			err := ValidateProjectName(tc.name)
			if tc.ok && err != nil {
				t.Errorf("ValidateProjectName(%q) = %v, want nil", tc.name, err)
			}
			if !tc.ok && err == nil {
				t.Errorf("ValidateProjectName(%q) = nil, want error", tc.name)
			}
		})
	}
}

func TestValidateFileTitleLength(t *testing.T) {
	long := make([]byte, 201)
	for i := range long {
		long[i] = 'a'
	}
	zf := ZddcFile{Title: string(long)}
	errs := ValidateFile(zf)
	if len(errs) != 1 || errs[0].Field != "title" {
		t.Fatalf("expected one title-length error, got %+v", errs)
	}
}
