package relaydev

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

// A relay board on the LAN signs its own certificate — there is no authority
// anywhere that could have signed it. httptest.NewTLSServer presents exactly
// that: a certificate from an unknown issuer, which is what the hardware does.
func selfSignedRelay(t *testing.T) (*httptest.Server, func() []string) {
	t.Helper()
	var mu sync.Mutex
	var got []string
	srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		mu.Lock()
		got = append(got, r.URL.Path)
		mu.Unlock()
		w.WriteHeader(http.StatusOK)
	}))
	t.Cleanup(srv.Close)
	return srv, func() []string {
		mu.Lock()
		defer mu.Unlock()
		return append([]string(nil), got...)
	}
}

// With the box ticked, the board answers.
func TestHTTPSRelayWithASelfSignedCertificate(t *testing.T) {
	srv, seen := selfSignedRelay(t)
	d := NewHTTPGeneric(nil, nil, srv.URL+"/on/{relay}", srv.URL+"/off/{relay}", "", "", 2, nil, true)
	if err := d.Set(context.Background(), 1, true); err != nil {
		t.Fatalf("Set over HTTPS: %v", err)
	}
	if paths := seen(); len(paths) != 1 || paths[0] != "/on/1" {
		t.Errorf("the board was asked for %v, want /on/1", paths)
	}
}

// Without it, the request is refused — and the refusal has to name the box.
//
// Go's own message, "x509: certificate signed by unknown authority", is
// accurate and tells an operator nothing about what to do next. This is the
// difference between a dead end and an instruction, and it is the whole reason
// the default can safely stay OFF.
func TestARefusedCertificateNamesTheSetting(t *testing.T) {
	srv, seen := selfSignedRelay(t)
	d := NewHTTPGeneric(nil, nil, srv.URL+"/on/{relay}", srv.URL+"/off/{relay}", "", "", 2, nil, false)
	err := d.Set(context.Background(), 1, true)
	if err == nil {
		t.Fatal("an unverifiable certificate was accepted with the box unticked")
	}
	if !strings.Contains(err.Error(), "self-signed") {
		t.Errorf("the refusal reads %q — it does not say which setting to change", err)
	}
	if len(seen()) != 0 {
		t.Error("the request reached the board despite the certificate being refused")
	}
}

// The box belongs to ONE board. An operator with a self-signed switch on the
// LAN and a second board reached through a proper HTTPS proxy must keep real
// verification on the second — that link crosses the internet, and it commands
// an antenna.
func TestAcceptingOneBoardsCertificateDoesNotAffectAnother(t *testing.T) {
	srv, _ := selfSignedRelay(t)
	lan := NewHTTPGeneric(nil, nil, srv.URL+"/on/{relay}", "", "", "", 1, nil, true)
	if err := lan.Set(context.Background(), 1, true); err != nil {
		t.Fatalf("the LAN board: %v", err)
	}
	strict := NewHTTPGeneric(nil, nil, srv.URL+"/on/{relay}", "", "", "", 1, nil, false)
	if err := strict.Set(context.Background(), 1, true); err == nil {
		t.Error("the second board accepted the certificate too — the setting is not per board")
	}
}
