package api

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

	"github.com/tidbcloud/aisupport/services/oncall/models"
	"github.com/tidbcloud/aisupport/services/oncall/service"

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

// buildAuthedRouter wires the real AuthMiddleware + ServiceKeyScopeGuard in front
// of a read route and a write route, mirroring the production middleware chain.
func buildAuthedRouter() *gin.Engine {
	r := gin.New()
	r.Use(AuthMiddleware())
	r.Use(ServiceKeyScopeGuard())
	r.GET("/api/v1/oncall/current", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true}) })
	r.POST("/api/v1/overlay/add", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true}) })
	return r
}

func doReq(r *gin.Engine, method, path, token string) *httptest.ResponseRecorder {
	req := httptest.NewRequest(method, path, nil)
	if token != "" {
		req.Header.Set("Authorization", "Bearer "+token)
	}
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	return w
}

func TestApiKeyHashingRoundTrip(t *testing.T) {
	setupAPITestDB(t)
	t.Setenv("AUTH0_ENABLED", "true")
	t.Setenv("ALLOWED_EMAIL_DOMAINS", "pingcap.com")

	plaintext, rec, err := service.CreateApiKey("alice@pingcap.com", "laptop", models.ApiKeyTypeUser, "")
	if err != nil {
		t.Fatalf("create key: %v", err)
	}
	if rec.KeyHash == plaintext || rec.KeyHash == "" {
		t.Fatalf("key must be stored hashed, got hash=%q plaintext=%q", rec.KeyHash, plaintext)
	}

	// A user key authenticates and may perform writes (inherits user permissions).
	r := buildAuthedRouter()
	if w := doReq(r, http.MethodPost, "/api/v1/overlay/add", plaintext); w.Code != http.StatusOK {
		t.Fatalf("user key write: expected 200, got %d body=%s", w.Code, w.Body.String())
	}

	// A bogus key is rejected.
	if w := doReq(r, http.MethodGet, "/api/v1/oncall/current", "oc_v2_deadbeef"); w.Code != http.StatusUnauthorized || !strings.Contains(w.Body.String(), ErrorCodeUnauthenticated) {
		t.Fatalf("bogus key: expected typed 401, got %d body=%s", w.Code, w.Body.String())
	}
}

func TestServiceKeyScopeGuard(t *testing.T) {
	setupAPITestDB(t)
	t.Setenv("AUTH0_ENABLED", "true")
	t.Setenv("ALLOWED_EMAIL_DOMAINS", "pingcap.com")

	readKey, _, err := service.CreateApiKey("admin@pingcap.com", "svc:alert", models.ApiKeyTypeService, service.ScopeOncallRead)
	if err != nil {
		t.Fatalf("create read key: %v", err)
	}
	writeKey, _, err := service.CreateApiKey("admin@pingcap.com", "svc:rotator", models.ApiKeyTypeService, service.ScopeOncallRead+" "+service.ScopeOncallWrite)
	if err != nil {
		t.Fatalf("create write key: %v", err)
	}

	r := buildAuthedRouter()

	// Read-only service key: read allowed, write denied.
	if w := doReq(r, http.MethodGet, "/api/v1/oncall/current", readKey); w.Code != http.StatusOK {
		t.Fatalf("service read: expected 200, got %d body=%s", w.Code, w.Body.String())
	}
	if w := doReq(r, http.MethodPost, "/api/v1/overlay/add", readKey); w.Code != http.StatusForbidden {
		t.Fatalf("read-only service write: expected 403, got %d body=%s", w.Code, w.Body.String())
	}

	// Write-scoped service key: write allowed.
	if w := doReq(r, http.MethodPost, "/api/v1/overlay/add", writeKey); w.Code != http.StatusOK {
		t.Fatalf("service write: expected 200, got %d body=%s", w.Code, w.Body.String())
	}
}

func TestAssertSafeAuthConfigFailsClosedInProd(t *testing.T) {
	// Bypass enabled in a prod-like env must refuse to boot.
	t.Setenv("APP_ENV", "prod")
	t.Setenv("AUTH0_ENABLED", "false")
	if err := service.AssertSafeAuthConfig(); err == nil {
		t.Fatal("expected AssertSafeAuthConfig to error when bypass is enabled in prod")
	}

	// Auth enforced in prod is fine.
	t.Setenv("AUTH0_ENABLED", "true")
	t.Setenv("DEV_BYPASS_AUTH", "false")
	if err := service.AssertSafeAuthConfig(); err != nil {
		t.Fatalf("expected no error with auth enforced in prod, got %v", err)
	}

	// Bypass in an explicit dev env is allowed.
	t.Setenv("APP_ENV", "dev")
	t.Setenv("AUTH0_ENABLED", "false")
	if err := service.AssertSafeAuthConfig(); err != nil {
		t.Fatalf("expected no error with bypass in dev, got %v", err)
	}
}
