package app

import (
	"testing"
)

// The attribute shapes here are copied from seeds/inventory.yaml. The original
// resolveHost looked for lan_ip / mesh.netbird.ip / mesh_ip, none of which a
// service or workstation actually carries — which is why 86 of 89 entities
// ended up with no checks.
func TestResolveHostAcceptsRealSeedShapes(t *testing.T) {
	cases := []struct {
		desc  string
		attrs map[string]any
		want  string
	}{
		{"lxc carries lan_ip", map[string]any{"lan_ip": "192.168.8.246"}, "192.168.8.246"},
		{
			"ws:mac-mini carries only a netbird fqdn",
			map[string]any{"mesh": map[string]any{"netbird": map[string]any{
				"fqdn": "mac-mini-234-17.netbird.selfhosted"}}},
			"mac-mini-234-17.netbird.selfhosted",
		},
		{
			"a netbird ip still wins over the fqdn",
			map[string]any{"mesh": map[string]any{"netbird": map[string]any{
				"ip": "100.122.0.10", "fqdn": "x.netbird.selfhosted"}}},
			"100.122.0.10",
		},
		{"public_host as a last resort", map[string]any{"public_host": "media.hubris.network"}, "media.hubris.network"},
		{"a service carries no address at all", map[string]any{
			"url": "https://media.hubris.network", "port": 8096}, ""},
		{"nil attrs", nil, ""},
	}

	for _, c := range cases {
		if got := resolveHost(c.attrs); got != c.want {
			t.Errorf("%s: resolveHost = %q, want %q", c.desc, got, c.want)
		}
	}
}

func TestHTTPURLPrefersAttributeThenName(t *testing.T) {
	cases := []struct {
		desc  string
		name  string
		attrs map[string]any
		want  string
	}{
		{"explicit url wins", "jellyfin",
			map[string]any{"url": "https://media.hubris.network"}, "https://media.hubris.network"},
		{"public_host becomes https", "jellyfin",
			map[string]any{"public_host": "media.hubris.network"}, "https://media.hubris.network"},
		// Ingress routes carry the hostname as the entity name and usually
		// declare no attributes at all.
		{"hostname-shaped name", "media.hubris.network", nil, "https://media.hubris.network"},
		{"a bare service name is not a hostname", "jellyfin", nil, ""},
	}

	for _, c := range cases {
		got := httpURL(CheckTarget{Name: c.name}, c.attrs)
		if got != c.want {
			t.Errorf("%s: httpURL = %q, want %q", c.desc, got, c.want)
		}
	}
}

func TestBuildKindReportsWhyItSkipped(t *testing.T) {
	// A declared kind that cannot be built must explain itself rather than
	// vanish — that silence is what hid the coverage gap.
	if defs, reason := buildKind(KindPing, CheckTarget{}, nil, "", "root", 22); len(defs) != 0 || reason == "" {
		t.Errorf("ping without a host should skip with a reason, got %d defs / %q", len(defs), reason)
	}
	if defs, reason := buildKind(KindProcess, CheckTarget{Name: ""}, nil, "10.0.0.1", "root", 22); len(defs) != 0 || reason == "" {
		t.Errorf("process without a name should skip with a reason, got %d defs / %q", len(defs), reason)
	}
	if defs, reason := buildKind("dns", CheckTarget{}, nil, "10.0.0.1", "root", 22); len(defs) != 0 || reason == "" {
		t.Errorf("an unimplemented kind should skip with a reason, got %d defs / %q", len(defs), reason)
	}
}

func TestBuildKindProcessPassesTheUnitName(t *testing.T) {
	// process_check.sh reads $1 and answers "no service name provided"
	// without it. checkdefaults always wrote args; nothing read them.
	defs, reason := buildKind(KindProcess, CheckTarget{Name: "jellyfin"}, nil, "10.0.0.1", "root", 22)
	if len(defs) != 1 {
		t.Fatalf("expected one process check, got %d (%s)", len(defs), reason)
	}
	if got := defs[0].Config["args"]; got != "jellyfin" {
		t.Errorf("process check args = %v, want jellyfin", got)
	}
	if got := defs[0].Config["script"]; got != "process_check.sh" {
		t.Errorf("process check script = %v", got)
	}
}

func TestBuildKindHTTPUsesAStatusRangeNotAnExactCode(t *testing.T) {
	// Most services sit behind Authentik and answer 302/401.
	defs, _ := buildKind(KindHTTP, CheckTarget{Name: "jellyfin"},
		map[string]any{"url": "https://media.hubris.network"}, "", "root", 22)
	if len(defs) != 1 {
		t.Fatalf("expected one http check, got %d", len(defs))
	}
	if got := defs[0].Config["max_status"]; got != 500 {
		t.Errorf("max_status = %v, want 500", got)
	}
	if _, exact := defs[0].Config["expected_status"]; exact {
		t.Error("default http checks must not pin an exact status")
	}
}

func TestBuildKindResourceExpandsToFourScripts(t *testing.T) {
	defs, _ := buildKind(KindResource, CheckTarget{}, nil, "10.0.0.1", "root", 22)
	if len(defs) != 4 {
		t.Fatalf("resource should expand to 4 checks, got %d", len(defs))
	}
	for _, d := range defs {
		if d.Kind != "ssh-script" {
			t.Errorf("resource check kind = %q, want ssh-script", d.Kind)
		}
		if d.Config["host"] != "10.0.0.1" {
			t.Errorf("resource check lost its host: %v", d.Config)
		}
	}
}
