package models

import (
	"strings"
	"testing"
)

func TestValidateRepoName_ValidRkeys(t *testing.T) {
	valid := []string{
		"myrepo",
		"MyRepo",
		"my-repo",
		"my_repo",
		"my.repo",
		"a",
		"repo123",
		strings.Repeat("a", 100),
	}
	for _, name := range valid {
		if err := ValidateRepoName(name); err != nil {
			t.Errorf("ValidateRepoName(%q) = %v, want nil", name, err)
		}
	}
}

func TestValidateRepoName_InvalidRkeys(t *testing.T) {
	cases := []struct {
		input  string
		substr string
	}{
		{"", "empty"},
		{strings.Repeat("a", 101), "100 characters"},
		{"has space", "alphanumeric"},
		{"has/slash", "invalid path"},
		{"has\\backslash", "invalid path"},
		{".dotprefix", "invalid path"},
		{"dotsuffix.", "invalid path"},
		{"two..dots", "sequential dots"},
		{"../traversal", "invalid path"},
		{"self", "reserved"},
		{"SELF", "reserved"},
	}
	for _, tc := range cases {
		err := ValidateRepoName(tc.input)
		if err == nil {
			t.Errorf("ValidateRepoName(%q) = nil, want error containing %q", tc.input, tc.substr)
			continue
		}
		if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(tc.substr)) {
			t.Errorf("ValidateRepoName(%q) = %q, want substring %q", tc.input, err.Error(), tc.substr)
		}
	}
}

func TestStripGitExt(t *testing.T) {
	cases := []struct{ in, want string }{
		{"repo.git", "repo"},
		{"repo", "repo"},
		{"repo.git.git", "repo.git"},
		{".git", ""},
	}
	for _, tc := range cases {
		if got := StripGitExt(tc.in); got != tc.want {
			t.Errorf("StripGitExt(%q) = %q, want %q", tc.in, got, tc.want)
		}
	}
}

func TestCosmeticName_NilWhenMatchesRkey(t *testing.T) {
	r := Repo{Name: "myrepo", Rkey: "myrepo"}
	rec := r.AsRecord()
	if rec.Name != nil {
		t.Errorf("cosmeticName should be nil when Name == Rkey, got %q", *rec.Name)
	}
}

func TestCosmeticName_PresentWhenDiffers(t *testing.T) {
	r := Repo{Name: "MyRepo", Rkey: "myrepo", Knot: "k"}
	rec := r.AsRecord()
	if rec.Name == nil {
		t.Fatal("cosmeticName should be non-nil when Name != Rkey")
	}
	if *rec.Name != "MyRepo" {
		t.Errorf("cosmeticName = %q, want %q", *rec.Name, "MyRepo")
	}
}

func TestRepoSlug(t *testing.T) {
	cases := []struct {
		name string
		repo Repo
		want string
	}{
		{"name set distinct from rkey", Repo{Name: "anemone", Rkey: "3kabc"}, "anemone"},
		{"name equals rkey", Repo{Name: "scallop", Rkey: "scallop"}, "scallop"},
		{"name empty falls to rkey", Repo{Name: "", Rkey: "whelk"}, "whelk"},
		{"both empty", Repo{}, ""},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			if got := c.repo.Slug(); got != c.want {
				t.Errorf("Slug() = %q, want %q", got, c.want)
			}
		})
	}
}

func TestValidateSpindle(t *testing.T) {
	cases := []struct {
		name    string
		raw     string
		dev     bool
		want    string
		wantErr bool
	}{
		{"empty means no spindle", "", false, "", false},
		{"whitespace only", "   ", false, "", false},
		{"bare hostname", "spindle.example.com", false, "spindle.example.com", false},
		{"scheme and trailing slash stripped", " https://Spindle.Example.com/ ", false, "spindle.example.com", false},
		{"localhost in dev", "localhost:6555", true, "localhost:6555", false},
		{"localhost in prod", "localhost:6555", false, "", true},
		{"plain http in prod", "http://spindle.example.com", false, "", true},
		{"link-local ip", "169.254.169.254", false, "", true},
		{"port on public host", "spindle.example.com:8443", false, "", true},
		{"single word", "spindle", false, "", true},
		{"not a host", "not a host", false, "", true},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			got, err := ValidateSpindle(c.raw, c.dev)
			if (err != nil) != c.wantErr {
				t.Fatalf("ValidateSpindle(%q, %v) error = %v, wantErr %v", c.raw, c.dev, err, c.wantErr)
			}
			if got != c.want {
				t.Errorf("ValidateSpindle(%q, %v) = %q, want %q", c.raw, c.dev, got, c.want)
			}
		})
	}
}
