package registry

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

const functionMD = `---
name: filter_slice
kind: function
lang: go
domain: core
version: "1.0.0"
purity: pure
signature: "func FilterSlice[T any](xs []T, pred func(T) bool) []T"
description: "Filtra un slice aplicando un predicado sin mutar el original."
tags: [slice, functional, generic]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "functions/core/filter_slice.go"
---

## Ejemplo

` + "```go" + `
evens := FilterSlice([]int{1, 2, 3, 4}, func(n int) bool { return n%2 == 0 })
` + "```" + `
`

const typeMD = `---
name: ohlcv
lang: go
domain: finance
version: "1.0.0"
algebraic: product
definition: |
  type OHLCV struct {
      Open   float64
      High   float64
      Low    float64
      Close  float64
      Volume float64
  }
description: "Vela de mercado con precios OHLCV."
tags: [finance, market, candle]
uses_types: []
file_path: "types/finance/ohlcv.go"
---

## Notas

Tipo producto.
`

const componentMD = `---
name: DataTable
kind: component
lang: typescript
domain: core
version: "1.0.0"
purity: impure
signature: "DataTable<T>(props: { data: T[] }): JSX.Element"
description: "Tabla de datos generica."
tags: [table, ui]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [react]
tested: false
tests: []
test_file_path: ""
file_path: "frontend/functions/ui/data_table.tsx"
props:
  - name: data
    type: "T[]"
    required: true
    description: "Array de datos"
emits: [onRowClick]
has_state: true
framework: react
variant: [default, compact]
---
`

func writeTempFile(t *testing.T, dir, name, content string) string {
	t.Helper()
	path := filepath.Join(dir, name)
	os.MkdirAll(filepath.Dir(path), 0o755)
	if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
		t.Fatal(err)
	}
	return path
}

func TestParseFunctionMD(t *testing.T) {
	path := writeTempFile(t, t.TempDir(), "filter_slice.md", functionMD)

	f, err := ParseFunctionMD(path, "")
	if err != nil {
		t.Fatal(err)
	}

	if f.ID != "filter_slice_go_core" {
		t.Errorf("id: got %q", f.ID)
	}
	if f.Kind != KindFunction {
		t.Errorf("kind: got %q", f.Kind)
	}
	if f.Purity != PurityPure {
		t.Errorf("purity: got %q", f.Purity)
	}
	if len(f.Tags) != 3 {
		t.Errorf("tags: got %d, want 3", len(f.Tags))
	}
	if f.Example == "" {
		t.Error("example should be extracted from body")
	}
}

func TestParseTypeMD(t *testing.T) {
	path := writeTempFile(t, t.TempDir(), "ohlcv.md", typeMD)

	typ, err := ParseTypeMD(path, "")
	if err != nil {
		t.Fatal(err)
	}

	if typ.ID != "ohlcv_go_finance" {
		t.Errorf("id: got %q", typ.ID)
	}
	if typ.Algebraic != AlgebraicProduct {
		t.Errorf("algebraic: got %q", typ.Algebraic)
	}
	if typ.Definition == "" {
		t.Error("definition should not be empty")
	}
}

func TestParseComponentMD(t *testing.T) {
	path := writeTempFile(t, t.TempDir(), "DataTable.md", componentMD)

	f, err := ParseFunctionMD(path, "")
	if err != nil {
		t.Fatal(err)
	}

	if f.Kind != KindComponent {
		t.Errorf("kind: got %q", f.Kind)
	}
	if f.Framework != "react" {
		t.Errorf("framework: got %q", f.Framework)
	}
	if len(f.Props) != 1 {
		t.Errorf("props: got %d, want 1", len(f.Props))
	}
	if f.HasState == nil || !*f.HasState {
		t.Error("has_state should be true")
	}
	if len(f.Emits) != 1 || f.Emits[0] != "onRowClick" {
		t.Errorf("emits: got %v", f.Emits)
	}
}

func TestParseMissingFrontmatter(t *testing.T) {
	path := writeTempFile(t, t.TempDir(), "bad.md", "# No frontmatter here\n")

	_, err := ParseFunctionMD(path, "")
	if err == nil {
		t.Error("expected error for missing frontmatter")
	}
}

func TestIndexFullCycle(t *testing.T) {
	root := t.TempDir()

	// Create function .md
	writeTempFile(t, root, "functions/core/filter_slice.md", functionMD)
	// Create type .md
	writeTempFile(t, root, "types/finance/ohlcv.md", typeMD)

	dbPath := filepath.Join(root, "registry.db")
	db, err := Open(dbPath)
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()

	result, err := Index(db, root)
	if err != nil {
		t.Fatal(err)
	}

	if result.Functions != 1 {
		t.Errorf("functions indexed: got %d, want 1", result.Functions)
	}
	if result.Types != 1 {
		t.Errorf("types indexed: got %d, want 1", result.Types)
	}
	if len(result.Errors) != 0 {
		t.Errorf("unexpected errors: %v", result.Errors)
	}
	if len(result.ValidationErrors) != 0 {
		t.Errorf("unexpected validation errors: %v", result.ValidationErrors)
	}

	// Verify searchable
	fns, err := db.SearchFunctions("filter", "", "", "", "")
	if err != nil {
		t.Fatal(err)
	}
	if len(fns) != 1 {
		t.Errorf("search 'filter': got %d, want 1", len(fns))
	}

	ts, err := db.SearchTypes("ohlcv", "", "")
	if err != nil {
		t.Fatal(err)
	}
	if len(ts) != 1 {
		t.Errorf("search 'ohlcv': got %d, want 1", len(ts))
	}

	// Re-index should be idempotent
	result2, err := Index(db, root)
	if err != nil {
		t.Fatal(err)
	}
	if result2.Functions != 1 || result2.Types != 1 {
		t.Error("re-index should produce same counts")
	}
}

const invalidPipelineMD = `---
name: bad_pipeline
kind: pipeline
lang: go
domain: core
version: "1.0.0"
purity: pure
description: "Pipeline puro sin uses_functions — debe fallar."
tags: []
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "functions/pipelines/bad.go"
---
`

func TestIndexRejectsInvalid(t *testing.T) {
	root := t.TempDir()

	// Valid function
	writeTempFile(t, root, "functions/core/filter_slice.md", functionMD)
	// Invalid pipeline (pure + empty uses_functions)
	writeTempFile(t, root, "functions/pipelines/bad.md", invalidPipelineMD)
	// Valid type
	writeTempFile(t, root, "types/finance/ohlcv.md", typeMD)

	dbPath := filepath.Join(root, "registry.db")
	db, err := Open(dbPath)
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()

	result, err := Index(db, root)
	if err != nil {
		t.Fatal(err)
	}

	// Valid entries should be indexed
	if result.Functions != 1 {
		t.Errorf("functions: got %d, want 1 (only the valid one)", result.Functions)
	}
	if result.Types != 1 {
		t.Errorf("types: got %d, want 1", result.Types)
	}

	// Invalid pipeline should produce validation error
	if len(result.ValidationErrors) == 0 {
		t.Error("expected validation errors for invalid pipeline")
	}
}
