package handlers

import (
	"encoding/json"
	"testing"

	"go.starlark.net/starlark"
)

// ── jsonToStarlark ────────────────────────────────────────────────────────────

func TestJSONToStarlark_String(t *testing.T) {
	v := jsonToStarlark("hello")
	s, ok := v.(starlark.String)
	if !ok || string(s) != "hello" {
		t.Errorf("expected starlark.String(hello), got %v (%T)", v, v)
	}
}

func TestJSONToStarlark_Int(t *testing.T) {
	v := jsonToStarlark(float64(42))
	i, ok := v.(starlark.Int)
	if !ok {
		t.Fatalf("expected starlark.Int, got %T", v)
	}
	n, _ := i.Int64()
	if n != 42 {
		t.Errorf("expected 42, got %d", n)
	}
}

func TestJSONToStarlark_Float(t *testing.T) {
	v := jsonToStarlark(3.14)
	if _, ok := v.(starlark.Float); !ok {
		t.Errorf("expected starlark.Float, got %T", v)
	}
}

func TestJSONToStarlark_Bool(t *testing.T) {
	if jsonToStarlark(true) != starlark.True {
		t.Error("expected starlark.True")
	}
	if jsonToStarlark(false) != starlark.False {
		t.Error("expected starlark.False")
	}
}

func TestJSONToStarlark_Nil(t *testing.T) {
	if jsonToStarlark(nil) != starlark.None {
		t.Error("expected starlark.None for nil")
	}
}

func TestJSONToStarlark_Dict(t *testing.T) {
	v := jsonToStarlark(map[string]interface{}{"key": "val"})
	d, ok := v.(*starlark.Dict)
	if !ok {
		t.Fatalf("expected *starlark.Dict, got %T", v)
	}
	got, found, _ := d.Get(starlark.String("key"))
	if !found || string(got.(starlark.String)) != "val" {
		t.Errorf("expected key=val in dict, got %v", got)
	}
}

func TestJSONToStarlark_List(t *testing.T) {
	v := jsonToStarlark([]interface{}{"a", "b"})
	l, ok := v.(*starlark.List)
	if !ok {
		t.Fatalf("expected *starlark.List, got %T", v)
	}
	if l.Len() != 2 {
		t.Errorf("expected 2 elements, got %d", l.Len())
	}
}

// ── starlarkValueToGo ─────────────────────────────────────────────────────────

func TestStarlarkValueToGo_String(t *testing.T) {
	v := starlarkValueToGo(starlark.String("hi"))
	if v != "hi" {
		t.Errorf("expected 'hi', got %v", v)
	}
}

func TestStarlarkValueToGo_Int(t *testing.T) {
	v := starlarkValueToGo(starlark.MakeInt(7))
	if v != int64(7) {
		t.Errorf("expected int64(7), got %v (%T)", v, v)
	}
}

func TestStarlarkValueToGo_Bool(t *testing.T) {
	if starlarkValueToGo(starlark.True) != true {
		t.Error("expected true")
	}
}

func TestStarlarkValueToGo_None(t *testing.T) {
	if starlarkValueToGo(starlark.None) != nil {
		t.Error("expected nil for starlark.None")
	}
}

func TestStarlarkValueToGo_Dict(t *testing.T) {
	d := starlark.NewDict(1)
	_ = d.SetKey(starlark.String("x"), starlark.MakeInt(1))
	v := starlarkValueToGo(d)
	m, ok := v.(map[string]interface{})
	if !ok {
		t.Fatalf("expected map, got %T", v)
	}
	if m["x"] != int64(1) {
		t.Errorf("expected x=1, got %v", m["x"])
	}
}

func TestStarlarkValueToGo_List(t *testing.T) {
	l := starlark.NewList([]starlark.Value{starlark.String("a"), starlark.String("b")})
	v := starlarkValueToGo(l)
	arr, ok := v.([]interface{})
	if !ok {
		t.Fatalf("expected []interface{}, got %T", v)
	}
	if len(arr) != 2 || arr[0] != "a" {
		t.Errorf("unexpected list: %v", arr)
	}
}

// ── RoundTrip: JSON → Starlark → Go → JSON ───────────────────────────────────

func TestRoundTrip_JSONToStarlarkToJSON(t *testing.T) {
	// Simulate what executeExtensionTool does: parse args, pass as Starlark, convert result back.
	argsJSON := `{"query": "hello", "limit": 10, "active": true}`
	var raw map[string]interface{}
	if err := json.Unmarshal([]byte(argsJSON), &raw); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	sv := jsonToStarlark(raw)
	got := starlarkValueToGo(sv)
	out, err := json.Marshal(got)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}
	// Verify key fields present
	var result map[string]interface{}
	if err := json.Unmarshal(out, &result); err != nil {
		t.Fatalf("unmarshal result: %v", err)
	}
	if result["query"] != "hello" {
		t.Errorf("expected query=hello, got %v", result["query"])
	}
	if result["active"] != true {
		t.Errorf("expected active=true, got %v", result["active"])
	}
}

// ── BuildExtToolMap ───────────────────────────────────────────────────────────

func TestBuildExtToolMap_Empty(t *testing.T) {
	// nil Packages store → returns nil (no panic)
	result := BuildExtToolMap(nil, storesWithExtData(newMemExtDataStore()), "user1")
	if result != nil && len(result) != 0 {
		t.Errorf("expected empty map for stores without Packages, got %v", result)
	}
}
