package main

import (
	"context"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

func TestPrepareHierarchyLayoutRequestNormalizesEdges(t *testing.T) {
	nodes := []Node{
		{ID: 0, TermType: "uri", IRI: "http://example.com/root"},
		{ID: 1, TermType: "uri", IRI: "http://example.com/child"},
		{ID: 2, TermType: "uri", IRI: "http://example.com/leaf"},
	}
	preds := NewPredicateDict([]string{"http://www.w3.org/2000/01/rdf-schema#subClassOf"})
	edges := []Edge{
		{Source: 1, Target: 0, PredicateID: 0},
		{Source: 1, Target: 0, PredicateID: 0},
		{Source: 2, Target: 2, PredicateID: 0},
		{Source: 2, Target: 1, PredicateID: 0},
	}

	prepared := prepareHierarchyLayoutRequest("http://example.com/root", nodes, edges, preds)

	if got, want := len(prepared.Request.Nodes), 3; got != want {
		t.Fatalf("len(request.nodes)=%d want %d", got, want)
	}
	if got, want := len(prepared.Request.Edges), 2; got != want {
		t.Fatalf("len(request.edges)=%d want %d", got, want)
	}
	if prepared.Request.Edges[0].ParentID != 0 || prepared.Request.Edges[0].ChildID != 1 {
		t.Fatalf("first normalized edge = %+v, want parent=0 child=1", prepared.Request.Edges[0])
	}
	if prepared.Request.Edges[1].ParentID != 1 || prepared.Request.Edges[1].ChildID != 2 {
		t.Fatalf("second normalized edge = %+v, want parent=1 child=2", prepared.Request.Edges[1])
	}
	if prepared.Request.Edges[0].PredicateIRI == nil || *prepared.Request.Edges[0].PredicateIRI == "" {
		t.Fatalf("expected predicate iri to be preserved")
	}
}

func TestApplyHierarchyLayoutResponsePreservesIDsAndRemapsRoutes(t *testing.T) {
	nodes := []Node{
		{ID: 0, TermType: "uri", IRI: "http://example.com/root"},
		{ID: 1, TermType: "uri", IRI: "http://example.com/child"},
		{ID: 2, TermType: "uri", IRI: "http://example.com/leaf"},
	}
	normalizedEdges := []Edge{
		{Source: 1, Target: 0, PredicateID: 0},
		{Source: 2, Target: 0, PredicateID: 0},
	}
	response := hierarchyLayoutResponse{
		Nodes: []hierarchyLayoutResponseNode{
			{NodeID: 0, X: 10, Y: 20},
			{NodeID: 2, X: 30, Y: 40},
		},
		RouteSegments: []hierarchyLayoutResponseRouteSegment{
			{
				EdgeIndex: 1,
				Kind:      "spiral",
				Points: []hierarchyLayoutResponseRoutePoint{
					{X: 10, Y: 20},
					{X: 30, Y: 40},
				},
			},
		},
	}

	result, err := applyHierarchyLayoutResponse(nodes, normalizedEdges, response)
	if err != nil {
		t.Fatalf("applyHierarchyLayoutResponse returned error: %v", err)
	}

	if got, want := len(result.Nodes), 2; got != want {
		t.Fatalf("len(nodes)=%d want %d", got, want)
	}
	if result.Nodes[0].ID != 0 || result.Nodes[1].ID != 2 {
		t.Fatalf("filtered node ids = [%d %d], want [0 2]", result.Nodes[0].ID, result.Nodes[1].ID)
	}
	if result.Nodes[0].X != 10 || result.Nodes[0].Y != 20 || result.Nodes[1].X != 30 || result.Nodes[1].Y != 40 {
		t.Fatalf("positions were not applied to filtered nodes: %+v", result.Nodes)
	}
	if got, want := len(result.Edges), 1; got != want {
		t.Fatalf("len(edges)=%d want %d", got, want)
	}
	if result.Edges[0] != normalizedEdges[1] {
		t.Fatalf("filtered edge = %+v, want %+v", result.Edges[0], normalizedEdges[1])
	}
	if got, want := len(result.RouteSegments), 1; got != want {
		t.Fatalf("len(route_segments)=%d want %d", got, want)
	}
	if result.RouteSegments[0].EdgeIndex != 0 {
		t.Fatalf("route edge index = %d want 0", result.RouteSegments[0].EdgeIndex)
	}
}

func TestRunHierarchyLayoutBridgeUsesConfiguredWorkingDirectory(t *testing.T) {
	tmpDir := t.TempDir()
	outputPath := filepath.Join(tmpDir, "pwd.txt")
	scriptPath := filepath.Join(tmpDir, "bridge.sh")
	script := "#!/bin/sh\npwd > \"" + outputPath + "\"\ncat >/dev/null\nprintf '{\"nodes\":[{\"node_id\":1,\"x\":10,\"y\":20,\"level\":0}],\"route_segments\":[]}'\n"
	if err := os.WriteFile(scriptPath, []byte(script), 0o755); err != nil {
		t.Fatalf("write script: %v", err)
	}

	cfg := Config{
		HierarchyLayoutBridgeBin:     scriptPath,
		HierarchyLayoutBridgeWorkdir: tmpDir,
		HierarchyLayoutTimeout:       2 * time.Second,
	}
	response, err := runHierarchyLayoutBridge(context.Background(), cfg, hierarchyLayoutRequest{
		RootIRI: "root",
		Nodes: []hierarchyLayoutRequestNode{
			{NodeID: 1, IRI: "root"},
		},
	})
	if err != nil {
		t.Fatalf("runHierarchyLayoutBridge returned error: %v", err)
	}
	if got, want := len(response.Nodes), 1; got != want {
		t.Fatalf("len(response.nodes)=%d want %d", got, want)
	}

	pwdBytes, err := os.ReadFile(outputPath)
	if err != nil {
		t.Fatalf("read pwd output: %v", err)
	}
	if got, want := strings.TrimSpace(string(pwdBytes)), tmpDir; got != want {
		t.Fatalf("bridge working directory=%q want %q", got, want)
	}
}

func TestRunHierarchyLayoutBridgeReturnsSvgWriteFailure(t *testing.T) {
	tmpDir := t.TempDir()
	scriptPath := filepath.Join(tmpDir, "bridge_fail.sh")
	script := "#!/bin/sh\ncat >/dev/null\necho 'failed to write SVG output: permission denied' >&2\nexit 1\n"
	if err := os.WriteFile(scriptPath, []byte(script), 0o755); err != nil {
		t.Fatalf("write script: %v", err)
	}

	cfg := Config{
		HierarchyLayoutBridgeBin:     scriptPath,
		HierarchyLayoutBridgeWorkdir: tmpDir,
		HierarchyLayoutTimeout:       2 * time.Second,
	}
	_, err := runHierarchyLayoutBridge(context.Background(), cfg, hierarchyLayoutRequest{
		RootIRI: "root",
	})
	if err == nil {
		t.Fatalf("expected hierarchy layout bridge error")
	}
	if !strings.Contains(err.Error(), "failed to write SVG output") {
		t.Fatalf("error=%q does not mention SVG write failure", err)
	}
}
