package relaydev

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

// The status parsers are the risky part; pin them against the documented wire
// formats using a stub HTTP server.

func TestWebswitchStatus(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !strings.HasPrefix(r.URL.Path, "/relaystate/get2/") {
			t.Errorf("unexpected status path %q", r.URL.Path)
		}
		_, _ = w.Write([]byte("1,1\n2,0\n3,1\n4,0\n5,1\n"))
	}))
	defer srv.Close()

	d := NewWebswitch(strings.TrimPrefix(srv.URL, "http://"))
	st, err := d.Status(context.Background())
	if err != nil {
		t.Fatal(err)
	}
	want := []bool{true, false, true, false, true}
	if len(st) != 5 {
		t.Fatalf("got %d relays, want 5", len(st))
	}
	for i := range want {
		if st[i] != want[i] {
			t.Errorf("relay %d = %v, want %v", i+1, st[i], want[i])
		}
	}
}

func TestWebswitchSetURL(t *testing.T) {
	var gotPath string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotPath = r.URL.Path
	}))
	defer srv.Close()
	d := NewWebswitch(strings.TrimPrefix(srv.URL, "http://"))
	if err := d.Set(context.Background(), 4, true); err != nil {
		t.Fatal(err)
	}
	if gotPath != "/relaycontrol/on/4" {
		t.Errorf("set path = %q, want /relaycontrol/on/4", gotPath)
	}
	_ = d.Set(context.Background(), 4, false)
}

func TestKMTronicStatus(t *testing.T) {
	// relay0 reserved (ignored); relay7 + relay8 ON.
	xmlBody := `<?xml version="1.0"?><response>` +
		`<relay0>0</relay0><relay1>0</relay1><relay2>0</relay2><relay3>0</relay3>` +
		`<relay4>0</relay4><relay5>0</relay5><relay6>0</relay6><relay7>1</relay7><relay8>1</relay8>` +
		`</response>`
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/status.xml" {
			t.Errorf("unexpected path %q", r.URL.Path)
		}
		_, _ = w.Write([]byte(xmlBody))
	}))
	defer srv.Close()

	d := NewKMTronic(strings.TrimPrefix(srv.URL, "http://"), "", "")
	st, err := d.Status(context.Background())
	if err != nil {
		t.Fatal(err)
	}
	if len(st) != 8 {
		t.Fatalf("got %d relays, want 8", len(st))
	}
	if st[6] != true || st[7] != true {
		t.Errorf("relay7/8 = %v/%v, want on/on", st[6], st[7])
	}
	for i := 0; i < 6; i++ {
		if st[i] {
			t.Errorf("relay %d unexpectedly on", i+1)
		}
	}
}

func TestKMTronicSetURL(t *testing.T) {
	var gotPath string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotPath = r.URL.Path
	}))
	defer srv.Close()
	d := NewKMTronic(strings.TrimPrefix(srv.URL, "http://"), "", "")
	if err := d.Set(context.Background(), 8, true); err != nil {
		t.Fatal(err)
	}
	if gotPath != "/FF0801" {
		t.Errorf("set path = %q, want /FF0801", gotPath)
	}
	_ = d.Set(context.Background(), 1, false) // → /FF0100
}

// Dingtian — the wire examples come straight from the IOT Relay Programming
// Manual V1.9.8.1 §3.1/§3.2. The two traps pinned here: the URL relay index is
// ZERO-based while the Device interface is 1-based, and a refusal (bad password
// or session) answers HTTP 200 with a non-zero first field.

func TestDingtianStatus(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/relay_cgi_load.cgi" {
			t.Errorf("unexpected status path %q", r.URL.Path)
		}
		// Manual's own example: ok, 4 relays, 1 on, 2 off, 3 on, 4 off.
		_, _ = w.Write([]byte("&0&4&1&0&1&0&"))
	}))
	defer srv.Close()

	d := NewDingtian(strings.TrimPrefix(srv.URL, "http://"), "", "", 2)
	st, err := d.Status(context.Background())
	if err != nil {
		t.Fatal(err)
	}
	// The board said 4 relays even though 2 were configured — its count wins.
	want := []bool{true, false, true, false}
	if len(st) != len(want) {
		t.Fatalf("got %d relays, want %d", len(st), len(want))
	}
	for i := range want {
		if st[i] != want[i] {
			t.Errorf("relay %d = %v, want %v", i+1, st[i], want[i])
		}
	}
	if d.Count() != 4 {
		t.Errorf("Count() = %d, want 4 (taken from the board)", d.Count())
	}
}

func TestDingtianSetUsesZeroBasedIndexAndSession(t *testing.T) {
	var gotQuery, gotCookie string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotQuery = r.URL.RawQuery
		gotCookie = r.Header.Get("Cookie")
		_, _ = w.Write([]byte("&0&0&2&1&0&"))
	}))
	defer srv.Close()

	d := NewDingtian(strings.TrimPrefix(srv.URL, "http://"), "12345678", "4660", 4)
	if err := d.Set(context.Background(), 3, true); err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(gotQuery, "relay=2") {
		t.Errorf("relay 3 must go out as relay=2 (zero-based); query was %q", gotQuery)
	}
	if !strings.Contains(gotQuery, "on=1") || !strings.Contains(gotQuery, "type=0") ||
		!strings.Contains(gotQuery, "pwd=4660") {
		t.Errorf("unexpected query %q", gotQuery)
	}
	if gotCookie != "session=12345678" {
		t.Errorf("session cookie = %q, want session=12345678", gotCookie)
	}
}

func TestDingtianSetRefusalIsAnError(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		// Manual §3.4.4: a bad session still answers 200 OK.
		_, _ = w.Write([]byte("&302&/&"))
	}))
	defer srv.Close()

	d := NewDingtian(strings.TrimPrefix(srv.URL, "http://"), "", "", 2)
	if err := d.Set(context.Background(), 1, true); err == nil {
		t.Fatal("a refused command answered HTTP 200 and was reported as success")
	}
}

func TestRelayBase(t *testing.T) {
	cases := map[string]string{
		"192.168.1.5":                  "http://192.168.1.5",
		"192.168.1.5:8080":             "http://192.168.1.5:8080",
		"https://relay.example.com":    "https://relay.example.com",
		"https://relay.example.com/":   "https://relay.example.com",
		"http://relay.example.com/sw/": "http://relay.example.com/sw",
		"  relay.local  ":              "http://relay.local",
	}
	for in, want := range cases {
		if got := relayBase(in); got != want {
			t.Errorf("relayBase(%q) = %q, want %q", in, got, want)
		}
	}
}
