package ontology

import (
	"errors"
	"testing"

	"github.com/dtoro/oikos/internal/core/domain"
)

func fixtureTree() *TypeTree {
	return &TypeTree{
		Types: map[string]TypeInfo{
			"entity":         {IsAbstract: true},
			"compute-entity": {Parent: "entity", IsAbstract: true},
			"machine":        {Parent: "compute-entity", IsAbstract: true},
			"proxmox-host":   {Parent: "machine", LifecycleID: "infrastructure"},
			"lxc":            {Parent: "compute-entity", LifecycleID: "infrastructure"},
			"service":        {Parent: "entity", LifecycleID: "infrastructure"},
			"document":       {Parent: "entity"}, // no lifecycle
		},
		RelTypes: map[string]RelTypeInfo{
			"hosts":      {SourceType: "machine", TargetType: "compute-entity", Cardinality: "one-to-many"},
			"provides":   {SourceType: "compute-entity", TargetType: "service", Cardinality: "one-to-many"},
			"depends-on": {SourceType: "service", TargetType: "service", Cardinality: "many-to-many"},
			"documents":  {SourceType: "document", TargetType: "entity", Cardinality: "many-to-one"},
		},
		Lifecycles: map[string]LifecycleInfo{
			"infrastructure": {
				States:       map[string]bool{"planned": true, "active": true, "destroyed": true},
				DefaultState: "active",
			},
		},
	}
}

func TestIsAWalksHierarchy(t *testing.T) {
	tree := fixtureTree()
	cases := []struct {
		typ, target string
		want        bool
	}{
		{"proxmox-host", "machine", true},
		{"proxmox-host", "compute-entity", true},
		{"proxmox-host", "entity", true},
		{"proxmox-host", "proxmox-host", true},
		{"lxc", "machine", false},
		{"service", "compute-entity", false},
		{"nonexistent", "entity", false},
	}
	for _, c := range cases {
		if got := tree.IsA(c.typ, c.target); got != c.want {
			t.Errorf("IsA(%q, %q) = %v, want %v", c.typ, c.target, got, c.want)
		}
	}
}

func TestValidateEntityRejectsAbstract(t *testing.T) {
	tree := fixtureTree()
	for _, abstract := range []string{"entity", "compute-entity", "machine"} {
		if err := tree.ValidateEntity(abstract, ""); !errors.Is(err, domain.ErrAbstractType) {
			t.Errorf("ValidateEntity(%q) = %v, want ErrAbstractType", abstract, err)
		}
	}
	if err := tree.ValidateEntity("lxc", "active"); err != nil {
		t.Errorf("ValidateEntity(lxc, active) = %v, want nil", err)
	}
}

func TestValidateEntityStates(t *testing.T) {
	tree := fixtureTree()
	if err := tree.ValidateEntity("lxc", "flying"); !errors.Is(err, domain.ErrInvalidTransition) {
		t.Errorf("bad state = %v, want ErrInvalidTransition", err)
	}
	// state on a type without a lifecycle is rejected
	if err := tree.ValidateEntity("document", "active"); !errors.Is(err, domain.ErrInvalidTransition) {
		t.Errorf("state without lifecycle = %v, want ErrInvalidTransition", err)
	}
	// unknown type
	if err := tree.ValidateEntity("ghost", ""); !errors.Is(err, domain.ErrNotFound) {
		t.Errorf("unknown type = %v, want ErrNotFound", err)
	}
}

func TestValidateEdgeHonorsInheritance(t *testing.T) {
	tree := fixtureTree()
	// proxmox-host is-a machine; lxc is-a compute-entity → valid
	if err := tree.ValidateEdge("hosts", "proxmox-host", "lxc"); err != nil {
		t.Errorf("hosts(proxmox-host→lxc) = %v, want nil", err)
	}
	// abstract endpoint declared, concrete descendant offered → valid
	if err := tree.ValidateEdge("provides", "lxc", "service"); err != nil {
		t.Errorf("provides(lxc→service) = %v, want nil", err)
	}
	// documents targets the root abstract 'entity' → anything is valid
	if err := tree.ValidateEdge("documents", "document", "proxmox-host"); err != nil {
		t.Errorf("documents(document→proxmox-host) = %v, want nil", err)
	}
	// service is not a machine → invalid source
	if err := tree.ValidateEdge("hosts", "service", "lxc"); !errors.Is(err, domain.ErrInvalidEdge) {
		t.Errorf("hosts(service→lxc) = %v, want ErrInvalidEdge", err)
	}
	// lxc is not a service → invalid target
	if err := tree.ValidateEdge("depends-on", "service", "lxc"); !errors.Is(err, domain.ErrInvalidEdge) {
		t.Errorf("depends-on(service→lxc) = %v, want ErrInvalidEdge", err)
	}
	// unknown relationship type
	if err := tree.ValidateEdge("teleports", "lxc", "service"); !errors.Is(err, domain.ErrNotFound) {
		t.Errorf("unknown rel type = %v, want ErrNotFound", err)
	}
}

func TestDefaultState(t *testing.T) {
	tree := fixtureTree()
	if got := tree.DefaultState("lxc"); got != "active" {
		t.Errorf("DefaultState(lxc) = %q, want active", got)
	}
	if got := tree.DefaultState("document"); got != "" {
		t.Errorf("DefaultState(document) = %q, want empty", got)
	}
}
