package registry

import (
	"strings"
	"testing"
)

func boolPtr(b bool) *bool { return &b }

func knownFns(ids ...string) map[string]bool {
	m := make(map[string]bool)
	for _, id := range ids {
		m[id] = true
	}
	return m
}

func knownTps(ids ...string) map[string]bool {
	return knownFns(ids...)
}

func TestValidateFunction_Valid(t *testing.T) {
	f := &Function{
		ID: "filter_slice_go_core", Name: "filter_slice", Kind: KindFunction,
		Lang: "go", Domain: "core", Purity: PurityPure,
		Description: "Filtra un slice", Version: "1.0.0",
	}
	if err := ValidateFunction(f, knownFns(), knownTps()); err != nil {
		t.Errorf("expected valid, got: %v", err)
	}
}

func TestValidateFunction_PipelineMustBeImpure(t *testing.T) {
	f := &Function{
		ID: "p_go_core", Name: "p", Kind: KindPipeline,
		Lang: "go", Domain: "core", Purity: PurityPure,
		Description: "bad pipeline", Version: "1.0.0",
		UsesFunctions: []string{"filter_slice_go_core"},
	}
	err := ValidateFunction(f, knownFns("filter_slice_go_core"), knownTps())
	if err == nil {
		t.Fatal("expected error")
	}
	if !strings.Contains(err.Error(), "pipeline must be impure") {
		t.Errorf("unexpected error: %v", err)
	}
}

func TestValidateFunction_PipelineNeedsUsesFunctions(t *testing.T) {
	f := &Function{
		ID: "p_go_core", Name: "p", Kind: KindPipeline,
		Lang: "go", Domain: "core", Purity: PurityImpure,
		Description: "bad pipeline", Version: "1.0.0",
		ErrorType: "error_go_core",
	}
	err := ValidateFunction(f, knownFns(), knownTps("error_go_core"))
	if err == nil {
		t.Fatal("expected error")
	}
	if !strings.Contains(err.Error(), "uses_functions cannot be empty") {
		t.Errorf("unexpected error: %v", err)
	}
}

func TestValidateFunction_PureNoReturnsOptional(t *testing.T) {
	f := &Function{
		ID: "f_go_core", Name: "f", Kind: KindFunction,
		Lang: "go", Domain: "core", Purity: PurityPure,
		Description: "bad", Version: "1.0.0",
		ReturnsOptional: true,
	}
	err := ValidateFunction(f, knownFns(), knownTps())
	if err == nil {
		t.Fatal("expected error")
	}
	if !strings.Contains(err.Error(), "returns_optional") {
		t.Errorf("unexpected error: %v", err)
	}
}

func TestValidateFunction_PureNoErrorType(t *testing.T) {
	f := &Function{
		ID: "f_go_core", Name: "f", Kind: KindFunction,
		Lang: "go", Domain: "core", Purity: PurityPure,
		Description: "bad", Version: "1.0.0",
		ErrorType: "error_go_core",
	}
	err := ValidateFunction(f, knownFns(), knownTps("error_go_core"))
	if err == nil {
		t.Fatal("expected error")
	}
	if !strings.Contains(err.Error(), "pure function cannot have error_type") {
		t.Errorf("unexpected error: %v", err)
	}
}

func TestValidateFunction_ImpureNeedsErrorType(t *testing.T) {
	f := &Function{
		ID: "f_go_io", Name: "f", Kind: KindFunction,
		Lang: "go", Domain: "io", Purity: PurityImpure,
		Description: "bad", Version: "1.0.0",
	}
	err := ValidateFunction(f, knownFns(), knownTps())
	if err == nil {
		t.Fatal("expected error")
	}
	if !strings.Contains(err.Error(), "impure function must declare error_type") {
		t.Errorf("unexpected error: %v", err)
	}
}

func TestValidateFunction_TestedNeedsTestFile(t *testing.T) {
	f := &Function{
		ID: "f_go_core", Name: "f", Kind: KindFunction,
		Lang: "go", Domain: "core", Purity: PurityPure,
		Description: "test", Version: "1.0.0",
		Tested: true,
	}
	err := ValidateFunction(f, knownFns(), knownTps())
	if err == nil {
		t.Fatal("expected error")
	}
	if !strings.Contains(err.Error(), "test_file_path") {
		t.Errorf("unexpected error: %v", err)
	}
}

func TestValidateFunction_NotTestedNoTests(t *testing.T) {
	f := &Function{
		ID: "f_go_core", Name: "f", Kind: KindFunction,
		Lang: "go", Domain: "core", Purity: PurityPure,
		Description: "test", Version: "1.0.0",
		Tested: false, Tests: []string{"ghost test"}, TestFilePath: "test.go",
	}
	err := ValidateFunction(f, knownFns(), knownTps())
	if err == nil {
		t.Fatal("expected error")
	}
	if !strings.Contains(err.Error(), "tested: false but tests is not empty") {
		t.Errorf("unexpected error: %v", err)
	}
}

func TestValidateFunction_OrphanRefs(t *testing.T) {
	f := &Function{
		ID: "p_go_core", Name: "p", Kind: KindPipeline,
		Lang: "go", Domain: "core", Purity: PurityImpure,
		Description: "pipeline", Version: "1.0.0",
		UsesFunctions: []string{"nonexistent_go_core"},
		UsesTypes:     []string{"ghost_go_core"},
		Returns:       []string{"phantom_go_core"},
		ErrorType:     "missing_go_core",
	}
	err := ValidateFunction(f, knownFns(), knownTps())
	if err == nil {
		t.Fatal("expected error")
	}
	if len(err.Errors) < 4 {
		t.Errorf("expected at least 4 errors, got %d: %v", len(err.Errors), err)
	}
}

func TestValidateFunction_ComponentRules(t *testing.T) {
	f := &Function{
		ID: "dt_typescript_core", Name: "DataTable", Kind: KindComponent,
		Lang: "typescript", Domain: "core", Purity: PurityImpure,
		Description: "table", Version: "1.0.0",
		HasState: boolPtr(true), Framework: "react",
	}
	if err := ValidateFunction(f, knownFns(), knownTps()); err != nil {
		t.Errorf("expected valid, got: %v", err)
	}

	// Missing framework
	f2 := *f
	f2.Framework = ""
	if err := ValidateFunction(&f2, knownFns(), knownTps()); err == nil {
		t.Error("expected error for missing framework")
	}

	// Returns should be empty
	f3 := *f
	f3.Returns = []string{"some_go_core"}
	if err := ValidateFunction(&f3, knownFns(), knownTps("some_go_core")); err == nil {
		t.Error("expected error for non-empty returns on component")
	}

	// has_state: true but pure
	f4 := *f
	f4.Purity = PurityPure
	if err := ValidateFunction(&f4, knownFns(), knownTps()); err == nil {
		t.Error("expected error for stateful pure component")
	}
}

func TestValidateFunction_AbsoluteFilePath(t *testing.T) {
	f := &Function{
		ID: "f_go_core", Name: "f", Kind: KindFunction,
		Lang: "go", Domain: "core", Purity: PurityPure,
		Description: "test", Version: "1.0.0",
		FilePath: "/absolute/path.go",
	}
	err := ValidateFunction(f, knownFns(), knownTps())
	if err == nil {
		t.Fatal("expected error for absolute file_path")
	}
}

func TestValidateType_Valid(t *testing.T) {
	typ := &Type{
		ID: "ohlcv_go_finance", Name: "ohlcv", Lang: "go", Domain: "finance",
		Algebraic: AlgebraicProduct, Description: "candle", Version: "1.0.0",
	}
	if err := ValidateType(typ, knownTps("ohlcv_go_finance")); err != nil {
		t.Errorf("expected valid, got: %v", err)
	}
}

func TestValidateType_BadAlgebraic(t *testing.T) {
	typ := &Type{
		ID: "t_go_core", Name: "t", Lang: "go", Domain: "core",
		Algebraic: "wrong", Description: "bad", Version: "1.0.0",
	}
	err := ValidateType(typ, knownTps("t_go_core"))
	if err == nil {
		t.Fatal("expected error")
	}
}

func TestValidateType_SelfReference(t *testing.T) {
	typ := &Type{
		ID: "t_go_core", Name: "t", Lang: "go", Domain: "core",
		Algebraic: AlgebraicProduct, Description: "self ref", Version: "1.0.0",
		UsesTypes: []string{"t_go_core"},
	}
	err := ValidateType(typ, knownTps("t_go_core"))
	if err == nil {
		t.Fatal("expected error")
	}
	if !strings.Contains(err.Error(), "cannot reference itself") {
		t.Errorf("unexpected error: %v", err)
	}
}

func TestValidateType_OrphanRef(t *testing.T) {
	typ := &Type{
		ID: "t_go_core", Name: "t", Lang: "go", Domain: "core",
		Algebraic: AlgebraicProduct, Description: "orphan ref", Version: "1.0.0",
		UsesTypes: []string{"nonexistent_go_core"},
	}
	err := ValidateType(typ, knownTps("t_go_core"))
	if err == nil {
		t.Fatal("expected error")
	}
	if !strings.Contains(err.Error(), "unknown type") {
		t.Errorf("unexpected error: %v", err)
	}
}
