package ontology

import (
	"testing"
)

func kinds(v ...string) *[]string {
	s := append([]string{}, v...)
	return &s
}

// monitoringTree mirrors the real shape of seeds/ontology.yaml: a declaration
// on an abstract type that concrete subtypes inherit, an explicit none on a
// topological type, a probeable type outside the infrastructure layer, and an
// undeclared infrastructure type (the ontology gap this is meant to catch).
func monitoringTree() *TypeTree {
	return &TypeTree{
		Types: map[string]TypeInfo{
			"entity":         {IsAbstract: true, Layer: "meta"},
			"compute-entity": {Parent: "entity", IsAbstract: true, Layer: "infrastructure"},
			"machine": {Parent: "compute-entity", IsAbstract: true, Layer: "infrastructure",
				Monitoring: kinds("ping", "resource")},
			"proxmox-host": {Parent: "machine", Layer: "infrastructure"},
			"workstation":  {Parent: "machine", Layer: "infrastructure"},
			"service":      {Parent: "entity", Layer: "infrastructure", Monitoring: kinds("http", "process")},
			"site":         {Parent: "entity", Layer: "infrastructure", Monitoring: kinds()},
			"vlan":         {Parent: "entity", Layer: "infrastructure"}, // undeclared: a gap
			"agent":        {Parent: "entity", Layer: "governance", Monitoring: kinds("http")},
			"signal":       {Parent: "entity", Layer: "cognition"},
			"document":     {Parent: "entity", Layer: "governance"},
		},
	}
}

func TestMonitoringResolvesThroughHierarchy(t *testing.T) {
	tree := monitoringTree()

	cases := []struct {
		typ        string
		wantKinds  []string
		wantDecl   bool
		wantSource string
		desc       string
	}{
		{"machine", []string{"ping", "resource"}, true, "machine", "declared on itself"},
		{"proxmox-host", []string{"ping", "resource"}, true, "machine", "inherited from abstract parent"},
		{"workstation", []string{"ping", "resource"}, true, "machine", "inherited by a sibling too"},
		{"service", []string{"http", "process"}, true, "service", "declared on itself"},
		{"site", nil, true, "site", "explicitly none — not a gap"},
		{"agent", []string{"http"}, true, "agent", "explicit declaration beats the layer default"},
		{"signal", nil, true, "", "cognition layer is implicitly none"},
		{"document", nil, true, "", "governance layer is implicitly none"},
		{"vlan", nil, false, "", "undeclared infrastructure type is a genuine gap"},
		{"nonexistent", nil, false, "", "unknown type resolves to undeclared"},
	}

	for _, c := range cases {
		got := tree.Monitoring(c.typ)
		if got.Declared != c.wantDecl {
			t.Errorf("%s (%s): Declared = %v, want %v", c.typ, c.desc, got.Declared, c.wantDecl)
		}
		if got.Source != c.wantSource {
			t.Errorf("%s (%s): Source = %q, want %q", c.typ, c.desc, got.Source, c.wantSource)
		}
		if len(got.Kinds) != len(c.wantKinds) {
			t.Errorf("%s (%s): Kinds = %v, want %v", c.typ, c.desc, got.Kinds, c.wantKinds)
			continue
		}
		for i, k := range c.wantKinds {
			if got.Kinds[i] != k {
				t.Errorf("%s (%s): Kinds[%d] = %q, want %q", c.typ, c.desc, i, got.Kinds[i], k)
			}
		}
	}
}

// The distinction between these two is what keeps coverageSweep from raising
// permanent, unresolvable signals against entities that are working as intended.
func TestMonitoringNoneIsNotTheSameAsUndeclared(t *testing.T) {
	tree := monitoringTree()

	site := tree.Monitoring("site")
	if !site.None() {
		t.Error("site declared `monitoring: none`, expected None() to report true")
	}

	vlan := tree.Monitoring("vlan")
	if vlan.None() {
		t.Error("vlan declared nothing at all — None() must not claim it opted out")
	}
	if vlan.Declared {
		t.Error("vlan is an undeclared infrastructure type; it should read as a gap")
	}
}

func TestMonitoringWants(t *testing.T) {
	tree := monitoringTree()

	svc := tree.Monitoring("service")
	if !svc.Wants("http") {
		t.Error("service should want an http check")
	}
	if svc.Wants("resource") {
		t.Error("service should not want a resource check")
	}
	if tree.Monitoring("site").Wants("http") {
		t.Error("an explicitly unmonitorable type wants nothing")
	}
}

func TestMonitoringSurvivesParentCycle(t *testing.T) {
	// The ingest rejects cycles; this guards the walker regardless.
	tree := &TypeTree{Types: map[string]TypeInfo{
		"a": {Parent: "b", Layer: "infrastructure"},
		"b": {Parent: "a", Layer: "infrastructure"},
	}}
	got := tree.Monitoring("a")
	if got.Declared {
		t.Errorf("cyclic chain declared nothing, got %+v", got)
	}
}
