package server

import (
	"bytes"
	"context"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// captureSlog swaps the default logger for a text handler writing to a buffer,
// restored on cleanup.
func captureSlog(t *testing.T) *bytes.Buffer {
	t.Helper()
	var buf bytes.Buffer
	prev := slog.Default()
	slog.SetDefault(slog.New(slog.NewTextHandler(&buf, nil)))
	t.Cleanup(func() { slog.SetDefault(prev) })
	return &buf
}

func TestWithRequestLogRecordsStatusAndBytes(t *testing.T) {
	buf := captureSlog(t)
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "boom", http.StatusNotFound)
	})

	rec := httptest.NewRecorder()
	withRequestLog(h).ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/migrations/x?format=text", nil))

	line := buf.String()
	assert.Contains(t, line, "http request")
	assert.Contains(t, line, "method=GET")
	assert.Contains(t, line, `uri="/migrations/x?format=text"`)
	assert.Contains(t, line, "status=404")
	assert.Contains(t, line, "bytes=5") // "boom" + newline from http.Error
	assert.Contains(t, line, "duration=")
}

func TestWithRequestLogDefaultsToOK(t *testing.T) {
	buf := captureSlog(t)
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hi"))
	})

	rec := httptest.NewRecorder()
	withRequestLog(h).ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/migrations", nil))

	assert.Contains(t, buf.String(), "status=200")
	assert.Contains(t, buf.String(), "bytes=2")
}

func TestWithRequestLogKeepsFirstStatus(t *testing.T) {
	buf := captureSlog(t)
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		w.WriteHeader(http.StatusOK)
	})

	rec := httptest.NewRecorder()
	withRequestLog(h).ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/x", nil))

	assert.Contains(t, buf.String(), "status=500")
}

func TestWithRequestLogForwardsFlush(t *testing.T) {
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		flusher, ok := w.(http.Flusher)
		require.True(t, ok)
		flusher.Flush()
	})

	rec := httptest.NewRecorder()
	withRequestLog(h).ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/migrations/x/logs", nil))

	assert.True(t, rec.Flushed)
}

func TestRegistryHandlerLogsRequests(t *testing.T) {
	buf := captureSlog(t)
	reg := NewRegistry(context.Background(), t.TempDir())
	h := RegistryHandler(reg, VersionInfo{})

	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/migrations", nil))
	assert.Equal(t, http.StatusOK, rec.Code)

	rec = httptest.NewRecorder()
	h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/migrations/nope", nil))
	assert.Equal(t, http.StatusNotFound, rec.Code)

	assert.Contains(t, buf.String(), "uri=/migrations ")
	assert.Contains(t, buf.String(), "uri=/migrations/nope")
	assert.Contains(t, buf.String(), "status=200")
	assert.Contains(t, buf.String(), "status=404")
}
