package reviewer

import (
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/kits/agentteam-insight/internal/graphbuilder/schema"
)

func TestReviewAndFixDropsUnsafeShapeDefaultsFieldsAndRecordsArtifact(t *testing.T) {
	root := t.TempDir()
	writeReviewFile(t, root, "src/app.go")
	graph := baseReviewGraph(root)
	graph.Nodes = append(graph.Nodes,
		schema.GraphNode{ID: "bad", Type: "weird", Name: "bad", FilePath: "src/bad.go", Summary: "bad", Tags: []string{}, Complexity: "simple"},
		schema.GraphNode{ID: "file:src/missing.go", Type: "file", Name: "missing.go", FilePath: "src/missing.go", Summary: "", Tags: nil, Complexity: "simple"},
	)
	graph.Edges = append(graph.Edges,
		schema.GraphEdge{Source: "file:src/app.go", Target: "missing", Type: "imports", Direction: "forward", Weight: 0.5},
		schema.GraphEdge{Source: "file:src/app.go", Target: "file:src/missing.go", Type: "imports", Direction: "forward", Weight: 0.5},
	)
	graph.Layers[0].NodeIDs = append(graph.Layers[0].NodeIDs, "file:src/missing.go")

	reviewed, artifact := ReviewAndFix(graph, Options{InputRoot: root, Mode: "review", Review: true, Now: time.Date(2026, 7, 3, 0, 0, 0, 0, time.UTC)})

	require.Equal(t, ArtifactSchema, artifact.Schema)
	require.Equal(t, "review", artifact.Mode)
	require.True(t, artifact.Review)
	require.Equal(t, "2026-07-03T00:00:00Z", artifact.GeneratedAt)
	requireCategory(t, artifact.Issues, "invalid-node-type")
	requireCategory(t, artifact.Issues, "dangling-edge")
	requireCategory(t, artifact.Warnings, "missing-tags")
	requireCategory(t, artifact.Warnings, "missing-summary")
	requireCategory(t, artifact.Warnings, "summary-degradation")
	requireCategory(t, artifact.Warnings, "missing-file")
	require.Equal(t, 1, artifact.Stats.DroppedInvalidNodes)
	require.Equal(t, 1, artifact.Stats.DroppedDanglingEdges)
	require.Equal(t, 1, artifact.Stats.DefaultedTags)
	require.Equal(t, 1, artifact.Stats.DefaultedSummaries)
	require.NotContains(t, nodeIDs(reviewed.Nodes), "bad")
	result := schema.ValidateGraph(reviewed)
	require.True(t, result.Success, result.Fatal)
}

func TestReviewAndFixLayer3QualityFindings(t *testing.T) {
	root := t.TempDir()
	writeReviewFile(t, root, "src/app.go")
	graph := baseReviewGraph(root)
	graph.Nodes = append(graph.Nodes,
		schema.GraphNode{ID: "service:docker:api", Type: "service", Name: "api", Summary: "service", Tags: []string{"docker"}, Complexity: "simple"},
		schema.GraphNode{ID: "file:src/api.go", Type: "service", Name: "api.go", FilePath: "src/app.go", Summary: "TODO", Tags: []string{}, Complexity: "simple"},
	)
	graph.Edges = append(graph.Edges,
		schema.GraphEdge{Source: "file:src/app.go", Target: "file:src/app.go", Type: "related", Direction: "forward", Weight: 0.5},
	)
	graph.Layers = append(graph.Layers, schema.Layer{ID: "empty", Name: "Empty", Description: "Empty", NodeIDs: []string{}})
	graph.Tour = []schema.TourStep{{Order: 1, Title: "Only", Description: "Only", NodeIDs: []string{"file:src/app.go"}}}

	_, artifact := ReviewAndFix(graph, Options{InputRoot: root, Mode: "full", Review: false, Now: time.Date(2026, 7, 3, 0, 0, 0, 0, time.UTC)})

	requireCategory(t, artifact.Issues, "empty-layer")
	requireCategory(t, artifact.Warnings, "tour-step-count")
	requireCategory(t, artifact.Warnings, "orphan-node")
	requireCategory(t, artifact.Warnings, "summary-degradation")
	requireCategory(t, artifact.Warnings, "self-referencing-edge")
	requireCategory(t, artifact.Warnings, "node-type-id-prefix")
	requireCategory(t, artifact.Warnings, "non-code-node-missing-edge")
}

func TestReviewAndFixSynthesizesTestedByStats(t *testing.T) {
	root := t.TempDir()
	writeReviewFile(t, root, "src/foo.go")
	writeReviewFile(t, root, "src/foo_test.go")
	graph := schema.KnowledgeGraph{
		Version: "1.0.0",
		Kind:    schema.KindCodebase,
		Project: reviewProject(),
		Nodes: []schema.GraphNode{
			{ID: "file:src/foo.go", Type: "file", Name: "foo.go", FilePath: "src/foo.go", Summary: "foo", Tags: []string{}, Complexity: "simple"},
			{ID: "file:src/foo_test.go", Type: "file", Name: "foo_test.go", FilePath: "src/foo_test.go", Summary: "foo test", Tags: []string{"test"}, Complexity: "simple"},
		},
		Edges:  []schema.GraphEdge{{Source: "file:src/foo.go", Target: "file:src/foo_test.go", Type: "related", Direction: "forward", Weight: 0.5}},
		Layers: []schema.Layer{{ID: "l", Name: "Layer", Description: "Layer", NodeIDs: []string{"file:src/foo.go", "file:src/foo_test.go"}}},
		Tour: []schema.TourStep{
			{Order: 1, Title: "1", Description: "1", NodeIDs: []string{"file:src/foo.go"}},
			{Order: 2, Title: "2", Description: "2", NodeIDs: []string{"file:src/foo_test.go"}},
			{Order: 3, Title: "3", Description: "3", NodeIDs: []string{"file:src/foo.go"}},
			{Order: 4, Title: "4", Description: "4", NodeIDs: []string{"file:src/foo_test.go"}},
			{Order: 5, Title: "5", Description: "5", NodeIDs: []string{"file:src/foo.go"}},
		},
	}

	reviewed, artifact := ReviewAndFix(graph, Options{InputRoot: root, Mode: "full"})

	require.Equal(t, 1, artifact.Stats.SynthesizedTestedBy)
	require.Equal(t, 1, artifact.Stats.TestedProductionNodes)
	requireReviewEdge(t, reviewed.Edges, "file:src/foo.go", "file:src/foo_test.go", "tested_by")
	require.Contains(t, tagsFor(reviewed.Nodes, "file:src/foo.go"), "tested")
}

func baseReviewGraph(root string) schema.KnowledgeGraph {
	return schema.KnowledgeGraph{
		Version: "1.0.0",
		Kind:    schema.KindCodebase,
		Project: reviewProject(),
		Nodes:   []schema.GraphNode{{ID: "file:src/app.go", Type: "file", Name: "app.go", FilePath: "src/app.go", Summary: "app", Tags: []string{"go"}, Complexity: "simple"}},
		Edges:   []schema.GraphEdge{},
		Layers:  []schema.Layer{{ID: "main", Name: "Main", Description: "Main", NodeIDs: []string{"file:src/app.go"}}},
		Tour: []schema.TourStep{
			{Order: 1, Title: "1", Description: "1", NodeIDs: []string{"file:src/app.go"}},
			{Order: 2, Title: "2", Description: "2", NodeIDs: []string{"file:src/app.go"}},
			{Order: 3, Title: "3", Description: "3", NodeIDs: []string{"file:src/app.go"}},
			{Order: 4, Title: "4", Description: "4", NodeIDs: []string{"file:src/app.go"}},
			{Order: 5, Title: "5", Description: "5", NodeIDs: []string{"file:src/app.go"}},
		},
	}
}

func reviewProject() schema.ProjectMeta {
	return schema.ProjectMeta{Name: "tiny", Languages: []string{"go"}, Frameworks: []string{}, Description: "tiny", AnalyzedAt: "2026-07-03T00:00:00Z", GitCommitHash: "abc"}
}

func writeReviewFile(t *testing.T, root, rel string) {
	t.Helper()
	path := filepath.Join(root, filepath.FromSlash(rel))
	require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
	require.NoError(t, os.WriteFile(path, []byte("package tiny\n"), 0o644))
}

func requireCategory(t *testing.T, findings []Finding, category string) {
	t.Helper()
	for _, finding := range findings {
		if finding.Category == category {
			return
		}
	}
	t.Fatalf("missing finding category %s in %#v", category, findings)
}

func nodeIDs(nodes []schema.GraphNode) map[string]struct{} {
	out := map[string]struct{}{}
	for _, node := range nodes {
		out[node.ID] = struct{}{}
	}
	return out
}

func requireReviewEdge(t *testing.T, edges []schema.GraphEdge, source, target, typ string) {
	t.Helper()
	for _, edge := range edges {
		if edge.Source == source && edge.Target == target && edge.Type == typ {
			return
		}
	}
	t.Fatalf("missing edge %s -> %s (%s) in %#v", source, target, typ, edges)
}

func tagsFor(nodes []schema.GraphNode, id string) []string {
	for _, node := range nodes {
		if node.ID == id {
			return node.Tags
		}
	}
	return nil
}
