package api

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/uopi/uopi/internal/access"
	"github.com/uopi/uopi/internal/datasource"
	"github.com/uopi/uopi/internal/datasource/synthetic"
	"github.com/uopi/uopi/internal/panelacl"
)

// TestExtractLogicBlock covers the three branches of the <logic> extractor.
func TestExtractLogicBlock(t *testing.T) {
	if got := extractLogicBlock([]byte(`<i><logic><n/></logic></i>`)); got != "<logic><n/></logic>" {
		t.Errorf("extract = %q", got)
	}
	if got := extractLogicBlock([]byte(`<i></i>`)); got != "" {
		t.Errorf("no logic: want empty, got %q", got)
	}
	if got := extractLogicBlock([]byte(`<i><logic>unterminated`)); got != "" {
		t.Errorf("unterminated: want empty, got %q", got)
	}
}

// TestDataTypeName covers every DataType→token mapping plus the default.
func TestDataTypeName(t *testing.T) {
	cases := map[datasource.DataType]string{
		datasource.TypeFloat64:      "float64",
		datasource.TypeFloat64Array: "float64[]",
		datasource.TypeString:       "string",
		datasource.TypeInt64:        "int64",
		datasource.TypeBool:         "bool",
		datasource.TypeEnum:         "enum",
		datasource.DataType(255):    "unknown",
	}
	for typ, want := range cases {
		if got := dataTypeName(typ); got != want {
			t.Errorf("dataTypeName(%v) = %q, want %q", typ, got, want)
		}
	}
}

// TestSynVisible covers each visibility branch of synVisible.
func TestSynVisible(t *testing.T) {
	cases := []struct {
		name   string
		def    synthetic.SignalDef
		user   string
		panel  string
		groups []string
		want   bool
	}{
		{"user own", synthetic.SignalDef{Visibility: "user", Owner: "alice"}, "alice", "", nil, true},
		{"user other", synthetic.SignalDef{Visibility: "user", Owner: "alice"}, "bob", "", nil, false},
		{"panel match", synthetic.SignalDef{Visibility: "panel", Panel: "p1"}, "bob", "p1", nil, true},
		{"panel mismatch", synthetic.SignalDef{Visibility: "panel", Panel: "p1"}, "bob", "p2", nil, false},
		{"global", synthetic.SignalDef{Visibility: "global"}, "", "", nil, true},
		{"legacy empty", synthetic.SignalDef{}, "", "", nil, true},
		{"group member", synthetic.SignalDef{Visibility: "group", Owner: "alice", Groups: []string{"ops"}}, "bob", "", []string{"ops"}, true},
		{"group outsider", synthetic.SignalDef{Visibility: "group", Owner: "alice", Groups: []string{"ops"}}, "bob", "", []string{"hr"}, false},
	}
	for _, tc := range cases {
		if got := synVisible(tc.def, tc.user, tc.panel, tc.groups); got != tc.want {
			t.Errorf("%s: synVisible = %v, want %v", tc.name, got, tc.want)
		}
	}
}

// TestPanelScope covers the nil/public→global, group, and private branches.
func TestPanelScope(t *testing.T) {
	if sc, _ := panelScope(nil); sc != access.ScopeGlobal {
		t.Errorf("nil acl: scope = %q, want global", sc)
	}
	if sc, _ := panelScope(&panelacl.PanelACL{Public: "read"}); sc != access.ScopeGlobal {
		t.Errorf("public acl: scope = %q, want global", sc)
	}
	sc, groups := panelScope(&panelacl.PanelACL{
		Grants: []panelacl.Grant{{Kind: "group", Name: "ops"}, {Kind: "user", Name: "x"}},
	})
	if sc != access.ScopeGroup || len(groups) != 1 || groups[0] != "ops" {
		t.Errorf("group acl: scope=%q groups=%v", sc, groups)
	}
	if sc, _ := panelScope(&panelacl.PanelACL{Owner: "alice"}); sc != access.ScopePrivate {
		t.Errorf("private acl: scope = %q, want private", sc)
	}
}

// TestClientIP covers the XFF, X-Real-IP, host:port, and bare-RemoteAddr cases.
func TestClientIP(t *testing.T) {
	mk := func(set func(*http.Request)) *http.Request {
		r := httptest.NewRequest(http.MethodGet, "/", nil)
		set(r)
		return r
	}
	if got := clientIP(mk(func(r *http.Request) { r.Header.Set("X-Forwarded-For", "1.2.3.4, 5.6.7.8") })); got != "1.2.3.4" {
		t.Errorf("XFF list: got %q", got)
	}
	if got := clientIP(mk(func(r *http.Request) { r.Header.Set("X-Forwarded-For", "9.9.9.9") })); got != "9.9.9.9" {
		t.Errorf("XFF single: got %q", got)
	}
	if got := clientIP(mk(func(r *http.Request) { r.Header.Set("X-Real-IP", "8.8.8.8") })); got != "8.8.8.8" {
		t.Errorf("X-Real-IP: got %q", got)
	}
	if got := clientIP(mk(func(r *http.Request) { r.RemoteAddr = "10.0.0.1:5555" })); got != "10.0.0.1" {
		t.Errorf("host:port: got %q", got)
	}
	if got := clientIP(mk(func(r *http.Request) { r.RemoteAddr = "bare-addr" })); got != "bare-addr" {
		t.Errorf("bare addr: got %q", got)
	}
}
