package main

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
)

func post(t *testing.T, handler http.Handler, path, body, auth string) *httptest.ResponseRecorder {
	t.Helper()
	req := httptest.NewRequest(http.MethodPost, path, bytes.NewBufferString(body))
	req.Header.Set("Content-Type", "application/json")
	if auth != "" {
		req.Header.Set("Authorization", "Bearer "+auth)
	}
	w := httptest.NewRecorder()
	handler.ServeHTTP(w, req)
	return w
}

// TestConnectReturnsValidConfig verifies that a well-formed request returns
// a 200 with all four §3.1 top-level blocks present.
func TestConnectReturnsValidConfig(t *testing.T) {
	h := makeHandler("test-token")
	w := post(t, h, "/v1/nodes/sg-1/connect", `{"device_id":"dev-001"}`, "test-token")

	if w.Code != http.StatusOK {
		t.Fatalf("want 200, got %d: %s", w.Code, w.Body.String())
	}
	var out map[string]any
	if err := json.Unmarshal(w.Body.Bytes(), &out); err != nil {
		t.Fatalf("response is not valid JSON: %v\nbody: %s", err, w.Body.String())
	}
	for _, key := range []string{"inbounds", "outbounds", "route", "dns"} {
		if _, ok := out[key]; !ok {
			t.Errorf("§3.1 block missing: %q", key)
		}
	}
	// outbounds must include urltest group named "auto"
	outs, _ := out["outbounds"].([]any)
	found := false
	for _, o := range outs {
		m, _ := o.(map[string]any)
		if m["type"] == "urltest" && m["tag"] == "auto" {
			found = true
		}
	}
	if !found {
		t.Error("outbounds: missing urltest group 'auto'")
	}
	// inbound must have strict_route:true (Kill-switch)
	ins, _ := out["inbounds"].([]any)
	if len(ins) == 0 {
		t.Fatal("inbounds is empty")
	}
	tun, _ := ins[0].(map[string]any)
	if tun["strict_route"] != true {
		t.Error("inbounds[0].strict_route must be true (Kill-switch)")
	}
}

// TestConnectRequiresAuth verifies that a missing / wrong token yields 401.
func TestConnectRequiresAuth(t *testing.T) {
	h := makeHandler("secret")
	cases := []struct{ name, token string }{
		{"no-header", ""},
		{"wrong-token", "wrong"},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			w := post(t, h, "/v1/nodes/sg-1/connect", `{"device_id":"x"}`, c.token)
			if w.Code != http.StatusUnauthorized {
				t.Errorf("want 401, got %d", w.Code)
			}
		})
	}
}

// TestConnectNoAuthCheck verifies that an empty token skips auth entirely.
func TestConnectNoAuthCheck(t *testing.T) {
	h := makeHandler("")
	w := post(t, h, "/v1/nodes/sg-1/connect", `{"device_id":"x"}`, "")
	if w.Code != http.StatusOK {
		t.Errorf("want 200, got %d", w.Code)
	}
}

// TestConnectRequiresDeviceID verifies that missing device_id yields 400.
func TestConnectRequiresDeviceID(t *testing.T) {
	h := makeHandler("")
	cases := []struct{ name, body string }{
		{"empty-object", `{}`},
		{"blank-id", `{"device_id":""}`},
		{"not-json", `not-json`},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodPost, "/v1/nodes/sg-1/connect", bytes.NewBufferString(c.body))
			rw := httptest.NewRecorder()
			h.ServeHTTP(rw, req)
			if rw.Code != http.StatusBadRequest {
				t.Errorf("want 400, got %d", rw.Code)
			}
		})
	}
}

// TestConnectMethodNotAllowed verifies that GET returns 405.
func TestConnectMethodNotAllowed(t *testing.T) {
	h := makeHandler("")
	req := httptest.NewRequest(http.MethodGet, "/v1/nodes/sg-1/connect", nil)
	w := httptest.NewRecorder()
	h.ServeHTTP(w, req)
	if w.Code != http.StatusMethodNotAllowed {
		t.Errorf("want 405, got %d", w.Code)
	}
}

// TestConnectWrongPath verifies that unrelated paths return 404.
func TestConnectWrongPath(t *testing.T) {
	h := makeHandler("")
	req := httptest.NewRequest(http.MethodPost, "/v1/nodes/sg-1/disconnect", nil)
	w := httptest.NewRecorder()
	h.ServeHTTP(w, req)
	if w.Code != http.StatusNotFound {
		t.Errorf("want 404, got %d", w.Code)
	}
}
