package openapi

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

	"github.com/gin-gonic/gin"
)

type specDocument struct {
	Swagger             string                     `json:"swagger"`
	Paths               map[string]json.RawMessage `json:"paths"`
	SecurityDefinitions map[string]json.RawMessage `json:"securityDefinitions"`
}

func serveSpec(t *testing.T, headers map[string]string) *httptest.ResponseRecorder {
	t.Helper()
	gin.SetMode(gin.TestMode)
	engine := gin.New()
	engine.GET("/api/v1/openapi.json", NewHandler().GetSpec)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/openapi.json", nil)
	for key, value := range headers {
		req.Header.Set(key, value)
	}
	rec := httptest.NewRecorder()
	engine.ServeHTTP(rec, req)
	return rec
}

func TestGetSpecServesEmbeddedSwagger(t *testing.T) {
	rec := serveSpec(t, nil)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d", rec.Code)
	}
	if ct := rec.Header().Get("Content-Type"); !strings.Contains(ct, "application/json") {
		t.Fatalf("expected JSON content type, got %q", ct)
	}
	if cc := rec.Header().Get("Cache-Control"); cc != "no-cache" {
		t.Fatalf("expected Cache-Control no-cache, got %q", cc)
	}
	if etag := rec.Header().Get("ETag"); etag == "" {
		t.Fatal("expected ETag header to be set")
	}

	var doc specDocument
	if err := json.Unmarshal(rec.Body.Bytes(), &doc); err != nil {
		t.Fatalf("response body is not valid JSON: %v", err)
	}
	if doc.Swagger != "2.0" {
		t.Fatalf("expected swagger 2.0, got %q", doc.Swagger)
	}
	if len(doc.Paths) != 38 {
		t.Fatalf("expected 38 paths, got %d", len(doc.Paths))
	}
	for _, key := range []string{"agentApiKey", "bearerAuth"} {
		if _, ok := doc.SecurityDefinitions[key]; !ok {
			t.Fatalf("expected securityDefinitions to contain %q", key)
		}
	}
}

func TestGetSpecReturnsNotModifiedOnMatchingETag(t *testing.T) {
	first := serveSpec(t, nil)
	etag := first.Header().Get("ETag")
	if etag == "" {
		t.Fatal("expected ETag header on initial response")
	}

	second := serveSpec(t, map[string]string{"If-None-Match": etag})
	if second.Code != http.StatusNotModified {
		t.Fatalf("expected status 304, got %d", second.Code)
	}
	if second.Body.Len() != 0 {
		t.Fatalf("expected empty body on 304, got %d bytes", second.Body.Len())
	}
}
