package codegen

import (
	"testing"

	"github.com/zzet/gortex/internal/graph"
)

func TestScan_Variants(t *testing.T) {
	cases := []struct {
		name      string
		src       string
		generated bool
		tool      string
		source    string
	}{
		{
			name:      "protoc-go",
			src:       "// Code generated by protoc-gen-go. DO NOT EDIT.\n// source: github.com/foo/bar/baz.proto\n\npackage bar\n",
			generated: true,
			tool:      "protoc-gen-go",
			source:    "github.com/foo/bar/baz.proto",
		},
		{
			name:      "stringer",
			src:       "// Code generated by \"stringer -type=Color\". DO NOT EDIT.\n\npackage colors\n",
			generated: true,
			tool:      `"stringer -type=Color"`,
		},
		{
			name:      "at-generated",
			src:       "// @generated by graphql-codegen\n\nexport const x = 1;\n",
			generated: true,
			tool:      "graphql-codegen",
		},
		{
			name:      "at-generated-bare",
			src:       "// @generated\nimport foo\n",
			generated: true,
		},
		{
			name:      "no-marker",
			src:       "package main\n\nfunc main() {}\n",
			generated: false,
		},
		{
			name:      "below-window",
			src:       "// 1\n// 2\n// 3\n// 4\n// 5\n// 6\n// 7\n// 8\n// 9\n// 10\n// 11\n// Code generated by foo. DO NOT EDIT.\n",
			generated: false,
		},
		{
			name:      "case-sensitive",
			src:       "// code generated automatically\n",
			generated: false,
		},
		{
			name:      "marker-in-prose-not-flagged",
			src:       "// Recognises the conventional Go \"Code generated by ...\" header in prose docs of this package.\n",
			generated: false,
		},
		{
			name: "tree-sitter-inline-marker",
			src:  "/* Automatically @generated by tree-sitter */\n\n#include\n",
			// tree-sitter wraps the marker in a /* */ block with a
			// short "Automatically" prefix; the maxMarkerOffset
			// threshold accepts it.
			generated: true,
			tool:      "tree-sitter",
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got := Scan([]byte(tc.src))
			if got.Generated != tc.generated {
				t.Fatalf("Generated = %v, want %v", got.Generated, tc.generated)
			}
			if got.Tool != tc.tool {
				t.Errorf("Tool = %q, want %q", got.Tool, tc.tool)
			}
			if got.Source != tc.source {
				t.Errorf("Source = %q, want %q", got.Source, tc.source)
			}
		})
	}
}

func TestBuildGraphArtifacts(t *testing.T) {
	t.Run("with source path", func(t *testing.T) {
		edges := BuildGraphArtifacts("pkg/foo.pb.go", Marker{
			Generated: true,
			Tool:      "protoc-gen-go",
			Source:    "github.com/x/y/foo.proto",
		})
		if len(edges) != 1 {
			t.Fatalf("expected 1 edge, got %d", len(edges))
		}
		e := edges[0]
		if e.Kind != graph.EdgeGeneratedBy {
			t.Errorf("edge kind = %q", e.Kind)
		}
		if e.From != "pkg/foo.pb.go" {
			t.Errorf("from = %q", e.From)
		}
		if e.To != "external::generator-source:github.com/x/y/foo.proto" {
			t.Errorf("to = %q", e.To)
		}
	})
	t.Run("tool only", func(t *testing.T) {
		edges := BuildGraphArtifacts("pkg/foo.go", Marker{
			Generated: true,
			Tool:      "stringer",
		})
		if edges[0].To != "external::generator-tool:stringer" {
			t.Errorf("to = %q", edges[0].To)
		}
	})
	t.Run("bare @generated", func(t *testing.T) {
		edges := BuildGraphArtifacts("pkg/foo.ts", Marker{Generated: true})
		if edges[0].To != "external::generator-unknown" {
			t.Errorf("to = %q", edges[0].To)
		}
	})
	t.Run("not generated", func(t *testing.T) {
		edges := BuildGraphArtifacts("pkg/foo.go", Marker{})
		if len(edges) != 0 {
			t.Errorf("expected nil, got %+v", edges)
		}
	})
}

func TestMarkFileNode(t *testing.T) {
	meta := map[string]any{}
	MarkFileNode(meta, Marker{
		Generated: true,
		Tool:      "stringer",
		Source:    "color.go",
	})
	if v, _ := meta["generated"].(bool); !v {
		t.Errorf("generated meta missing")
	}
	if meta["generated_by"] != "stringer" {
		t.Errorf("generated_by = %v", meta["generated_by"])
	}
	if meta["generated_from"] != "color.go" {
		t.Errorf("generated_from = %v", meta["generated_from"])
	}
}

func TestMarkFileNode_NoOpWhenNotGenerated(t *testing.T) {
	meta := map[string]any{}
	MarkFileNode(meta, Marker{})
	if len(meta) != 0 {
		t.Errorf("meta should stay empty, got %+v", meta)
	}
}
