package testutil

import (
	"bytes"
	"context"
	"net/http"
	"net/http/httptest"
	"sync"
	"testing"
	"time"

	"galaxy/gateway/internal/logging"
	"galaxy/gateway/internal/telemetry"

	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// LogBuffer is a concurrency-safe in-memory buffer used by observability
// tests.
type LogBuffer struct {
	mu  sync.Mutex
	buf bytes.Buffer
}

// Write appends p to the buffer.
func (b *LogBuffer) Write(p []byte) (int, error) {
	b.mu.Lock()
	defer b.mu.Unlock()

	return b.buf.Write(p)
}

// String returns the current buffer contents.
func (b *LogBuffer) String() string {
	b.mu.Lock()
	defer b.mu.Unlock()

	return b.buf.String()
}

// NewObservedLogger constructs a JSON zap logger that writes into an in-memory
// buffer suitable for log assertions.
func NewObservedLogger(t *testing.T) (*zap.Logger, *LogBuffer) {
	t.Helper()

	buffer := &LogBuffer{}
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.TimeKey = "timestamp"
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder

	core := zapcore.NewCore(
		zapcore.NewJSONEncoder(encoderConfig),
		zapcore.Lock(zapcore.AddSync(buffer)),
		zap.DebugLevel,
	)

	logger := zap.New(core)
	t.Cleanup(func() {
		require.NoError(t, logging.Sync(logger))
	})

	return logger, buffer
}

// NewTelemetryRuntime constructs a telemetry runtime for tests and shuts it
// down automatically.
func NewTelemetryRuntime(t *testing.T, logger *zap.Logger) *telemetry.Runtime {
	t.Helper()

	runtime, err := telemetry.New(context.Background(), logger)
	require.NoError(t, err)

	t.Cleanup(func() {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()
		require.NoError(t, runtime.Shutdown(ctx))
	})

	return runtime
}

// ScrapeMetrics returns the Prometheus exposition produced by handler.
func ScrapeMetrics(t *testing.T, handler http.Handler) string {
	t.Helper()

	req := httptest.NewRequest(http.MethodGet, "/metrics", nil)
	recorder := httptest.NewRecorder()
	handler.ServeHTTP(recorder, req)
	require.Equal(t, http.StatusOK, recorder.Code)

	return recorder.Body.String()
}
