package main

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

	"github.com/go-chi/chi/v5"

	"git.agentview.dev/profit/golangLAKEHOUSE/internal/matrix"
)

// newTestRouter builds the matrixd router with a Retriever pointing at
// unreachable URLs. Contract-drift assertions in this file fire BEFORE
// any retriever call, so the unreachable-upstream behavior only matters
// for tests that exercise the success path (none here).
func newTestRouter(t *testing.T) http.Handler {
	t.Helper()
	h := &handlers{r: matrix.New("http://127.0.0.1:0", "http://127.0.0.1:0")}
	r := chi.NewRouter()
	h.register(r)
	return r
}

// TestPlaybookRecord_OldFieldNameRejected locks against a regression of
// the 2026-04-30 driver/matrixd contract drift: the playbook_lift driver
// briefly sent `{"query": ...}` while matrixd parsed `{"query_text": ...}`.
// Empty QueryText fails Validate() with "query_text required", which is
// the exact 400 the harness saw. If anyone renames the JSON tag, this
// test catches it before the harness has to.
func TestPlaybookRecord_OldFieldNameRejected(t *testing.T) {
	r := newTestRouter(t)
	body := []byte(`{"query":"x","answer_id":"y","answer_corpus":"z","score":1.0}`)
	req := httptest.NewRequest("POST", "/matrix/playbooks/record", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusBadRequest {
		t.Fatalf("expected 400 for old field name, got %d (body=%s)", w.Code, w.Body.String())
	}
	if !strings.Contains(w.Body.String(), "query_text required") {
		t.Errorf("expected validation error to mention query_text, got %q", w.Body.String())
	}
}

// TestPlaybookRecord_CurrentFieldName proves the right field name parses
// and reaches the retriever. We can't assert 200 without a live retriever,
// but we CAN assert the response is NOT a 400 from the validate step —
// which is the drift-detector counterpart to the test above.
func TestPlaybookRecord_CurrentFieldName(t *testing.T) {
	r := newTestRouter(t)
	body, _ := json.Marshal(map[string]any{
		"query_text":    "forklift operator OSHA-30",
		"answer_id":     "worker_42",
		"answer_corpus": "workers",
		"score":         1.0,
		"tags":          []string{"reality-test"},
	})
	req := httptest.NewRequest("POST", "/matrix/playbooks/record", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	// Retriever will fail (unreachable upstream); expected outcomes are
	// 502 (bad gateway, mapped from upstream HTTP error) or 500 (network
	// error). Anything that's NOT a 400 means we cleared validation.
	if w.Code == http.StatusBadRequest {
		t.Errorf("valid request rejected at validation step: %d %s", w.Code, w.Body.String())
	}
}

// TestPlaybookRecord_ScoreOutOfRange locks the score-bounds invariant
// from internal/matrix/playbook.go. Negative or >1.0 scores must 400.
func TestPlaybookRecord_ScoreOutOfRange(t *testing.T) {
	r := newTestRouter(t)
	for _, s := range []float64{-0.1, 1.1, 99} {
		body, _ := json.Marshal(map[string]any{
			"query_text":    "x",
			"answer_id":     "y",
			"answer_corpus": "z",
			"score":         s,
		})
		req := httptest.NewRequest("POST", "/matrix/playbooks/record", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)
		if w.Code != http.StatusBadRequest {
			t.Errorf("score=%v should be rejected, got %d", s, w.Code)
		}
	}
}

// TestRelevance_EmptyChunks locks the explicit empty-chunks 400 in
// handleRelevance. Keeps callers from silently getting an empty result
// when their request was malformed.
func TestRelevance_EmptyChunks(t *testing.T) {
	r := newTestRouter(t)
	body := []byte(`{"focus":{},"chunks":[]}`)
	req := httptest.NewRequest("POST", "/matrix/relevance", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400 on empty chunks, got %d (body=%s)", w.Code, w.Body.String())
	}
}

// TestRoutesMounted asserts that every route in handlers.register(r)
// resolves to a handler — i.e. none of them would 404 against a request.
// Closes R-005 for matrixd (router-level wiring test).
func TestRoutesMounted(t *testing.T) {
	r := newTestRouter(t)
	cases := []struct {
		method, path string
	}{
		{"POST", "/matrix/search"},
		{"GET", "/matrix/corpora"},
		{"POST", "/matrix/relevance"},
		{"POST", "/matrix/downgrade"},
		{"POST", "/matrix/playbooks/record"},
		{"POST", "/matrix/playbooks/bulk"},
	}
	for _, tc := range cases {
		t.Run(tc.method+" "+tc.path, func(t *testing.T) {
			req := httptest.NewRequest(tc.method, tc.path, bytes.NewReader([]byte(`{}`)))
			req.Header.Set("Content-Type", "application/json")
			w := httptest.NewRecorder()
			r.ServeHTTP(w, req)
			if w.Code == http.StatusNotFound {
				t.Errorf("%s %s returned 404 — route not mounted", tc.method, tc.path)
			}
			if w.Code == http.StatusMethodNotAllowed {
				t.Errorf("%s %s returned 405 — wrong method registered", tc.method, tc.path)
			}
		})
	}
}
