package relaydev

import (
	"context"
	"net/http"
	"net/http/httptest"
	"strings"
	"sync"
	"testing"
)

// The pattern form: one URL pair for the whole board, {relay} substituted.
func TestHTTPGenericPattern(t *testing.T) {
	var mu sync.Mutex
	var got []string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		mu.Lock()
		got = append(got, r.URL.String())
		mu.Unlock()
	}))
	defer srv.Close()

	d := NewHTTPGeneric(nil, nil,
		srv.URL+"/relay?n={relay}&state=on",
		srv.URL+"/relay?n={relay}&state=off", "", "", 4, nil, false)
	if err := d.Set(context.Background(), 2, true); err != nil {
		t.Fatalf("Set on: %v", err)
	}
	if err := d.Set(context.Background(), 3, false); err != nil {
		t.Fatalf("Set off: %v", err)
	}
	mu.Lock()
	defer mu.Unlock()
	want := []string{"/relay?n=2&state=on", "/relay?n=3&state=off"}
	if strings.Join(got, " ") != strings.Join(want, " ") {
		t.Errorf("requested %v, want %v", got, want)
	}
}

// The per-relay form, which is the reason this driver exists: a hand-made
// switch often has URLs with nothing in common between channels, and no
// pattern can express that.
func TestHTTPGenericPerRelayURLsWinOverThePattern(t *testing.T) {
	var mu sync.Mutex
	var got []string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		mu.Lock()
		got = append(got, r.URL.Path)
		mu.Unlock()
	}))
	defer srv.Close()

	d := NewHTTPGeneric(
		[]string{srv.URL + "/FF0101", "", srv.URL + "/weird/on"},
		[]string{srv.URL + "/FF0100", "", ""},
		srv.URL+"/pattern/on/{relay}", srv.URL+"/pattern/off/{relay}", "", "", 3, nil, false)

	_ = d.Set(context.Background(), 1, true)  // its own URL
	_ = d.Set(context.Background(), 2, true)  // empty → falls back to the pattern
	_ = d.Set(context.Background(), 3, false) // no OFF of its own → pattern
	mu.Lock()
	defer mu.Unlock()
	want := []string{"/FF0101", "/pattern/on/2", "/pattern/off/3"}
	if strings.Join(got, " ") != strings.Join(want, " ") {
		t.Errorf("requested %v, want %v", got, want)
	}
}

// {value} is the relay's LABEL: a switch addressed by antenna name rather than
// by channel number is one pattern instead of eight URLs.
func TestHTTPGenericValueIsTheRelayLabel(t *testing.T) {
	var mu sync.Mutex
	var got []string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		mu.Lock()
		got = append(got, r.URL.String())
		mu.Unlock()
	}))
	defer srv.Close()

	d := NewHTTPGeneric(
		[]string{srv.URL + "/relay?on={value}"}, // per-relay URL
		nil,
		"", srv.URL+"/relay?off={value}", // and the pattern, for the other direction
		"", "", 3, []string{"Ant1", "Beam 20m", ""}, false)
	_ = d.Set(context.Background(), 1, true)
	_ = d.Set(context.Background(), 2, false)
	mu.Lock()
	defer mu.Unlock()
	// The space in "Beam 20m" must go out as %20 — a "+" would be a literal plus
	// in a path, and this substitution can land in either half of a URL.
	want := []string{"/relay?on=Ant1", "/relay?off=Beam%2020m"}
	if strings.Join(got, " ") != strings.Join(want, " ") {
		t.Errorf("requested %v, want %v", got, want)
	}
}

// {relay-1} for a board whose channels are numbered from zero.
func TestHTTPGenericRelayOffset(t *testing.T) {
	var mu sync.Mutex
	var got []string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		mu.Lock()
		got = append(got, r.URL.Path)
		mu.Unlock()
	}))
	defer srv.Close()

	d := NewHTTPGeneric(nil, nil,
		srv.URL+"/set0/{relay-1}/1", srv.URL+"/set0/{relay-1}/0", "", "", 4, nil, false)
	_ = d.Set(context.Background(), 1, true)
	_ = d.Set(context.Background(), 4, false)
	mu.Lock()
	defer mu.Unlock()
	want := []string{"/set0/0/1", "/set0/3/0"}
	if strings.Join(got, " ") != strings.Join(want, " ") {
		t.Errorf("requested %v, want %v", got, want)
	}
}

// A URL that uses {value} on an unlabelled relay would go out as "?on=" — an
// empty parameter, which most boards answer with a cheerful 200 and no
// movement. It must be refused, and the message must say the label is what is
// missing.
func TestHTTPGenericRefusesValueWithoutALabel(t *testing.T) {
	d := NewHTTPGeneric(nil, nil, "http://x/relay?on={value}", "", "", "", 2, []string{"", ""}, false)
	err := d.Set(context.Background(), 1, true)
	if err == nil || !strings.Contains(err.Error(), "label") {
		t.Errorf("err = %v, want it to name the missing label", err)
	}
}

// A URL typed without a scheme must still be sent — the named boards take a
// bare host and add http:// themselves, and this one has to behave the same.
// https:// is left exactly as typed.
func TestHTTPGenericSuppliesTheScheme(t *testing.T) {
	for _, c := range []struct{ in, want string }{
		{"192.168.1.9/Set0/1", "http://192.168.1.9/Set0/1"},
		{"http://192.168.1.9/x", "http://192.168.1.9/x"},
		{"https://relay.example.com/x", "https://relay.example.com/x"},
		{"HTTPS://relay.example.com/x", "HTTPS://relay.example.com/x"},
	} {
		if got := withScheme(c.in); got != c.want {
			t.Errorf("withScheme(%q) = %q, want %q", c.in, got, c.want)
		}
	}
}

// A switch with the ON URLs filled and OFF left empty latches. The error has to
// name the direction, or the operator cannot tell which half is missing.
func TestHTTPGenericNamesTheMissingDirection(t *testing.T) {
	d := NewHTTPGeneric([]string{"http://x/on"}, nil, "", "", "", "", 1, nil, false)
	err := d.Set(context.Background(), 1, false)
	if err == nil || !strings.Contains(err.Error(), "OFF") {
		t.Errorf("err = %v, want it to name the OFF direction", err)
	}
}

// Status reports what was commanded: these boxes have nothing to read.
func TestHTTPGenericRemembersWhatItCommanded(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
	defer srv.Close()
	d := NewHTTPGeneric(nil, nil, srv.URL+"/on/{relay}", srv.URL+"/off/{relay}", "", "", 3, nil, false)
	_ = d.Set(context.Background(), 2, true)
	st, err := d.Status(context.Background())
	if err != nil {
		t.Fatalf("Status: %v", err)
	}
	if len(st) != 3 || st[0] || !st[1] || st[2] {
		t.Errorf("state = %v, want only relay 2 on", st)
	}
}
