package handler

import (
	"baron-sso-backend/internal/domain"
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gofiber/fiber/v2"
	"github.com/stretchr/testify/assert"
	"gorm.io/gorm"
)

// Mock DB for ApiKey tests using a real GORM instance but with a hijacked connection
// or just a simple mock if we only check nil.
// For ApiKeyHandler, it uses DB for Create/List/Delete.

func TestApiKeyHandler_CreateApiKey(t *testing.T) {
	app := fiber.New()
	// ApiKeyHandler requires a valid DB connection to perform h.DB.Create
	// Since we don't have a real DB here, we'll check if it fails gracefully
	// or we can use sqlite in-memory for a more realistic test.
	h := &ApiKeyHandler{DB: nil} // Testing ServiceUnavailable

	app.Post("/api-keys", h.CreateApiKey)

	input := map[string]any{
		"name":   "M2M Test",
		"scopes": []string{"read", "write"},
	}
	body, _ := json.Marshal(input)

	req := httptest.NewRequest("POST", "/api-keys", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	resp, _ := app.Test(req)

	assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
}

func TestApiKeyHandler_Validation(t *testing.T) {
	app := fiber.New()
	// Using a dummy DB pointer to pass the nil check
	h := &ApiKeyHandler{DB: &gorm.DB{}}

	app.Post("/api-keys", h.CreateApiKey)

	// Missing name
	input := map[string]any{
		"scopes": []string{"read"},
	}
	body, _ := json.Marshal(input)

	req := httptest.NewRequest("POST", "/api-keys", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	resp, _ := app.Test(req)

	assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
}

func TestApiKeyHandler_UpdateApiKeyScopesRequiresDatabase(t *testing.T) {
	app := fiber.New()
	h := &ApiKeyHandler{DB: nil}

	app.Patch("/api-keys/:id", h.UpdateApiKey)

	body, _ := json.Marshal(map[string]any{
		"scopes": []string{"org-context:read"},
	})
	req := httptest.NewRequest("PATCH", "/api-keys/api-key-id", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	resp, _ := app.Test(req)

	assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
}

func TestApiKeyHandler_RotateApiKeySecretRequiresDatabase(t *testing.T) {
	app := fiber.New()
	h := &ApiKeyHandler{DB: nil}

	app.Post("/api-keys/:id/secret/rotate", h.RotateApiKeySecret)

	req := httptest.NewRequest("POST", "/api-keys/api-key-id/secret/rotate", nil)
	resp, _ := app.Test(req)

	assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
}

func TestApiKeyWithUpdatedScopesPreservesClientID(t *testing.T) {
	key := domain.ApiKey{
		ID:               "api-key-id",
		Name:             "M2M Test",
		ClientID:         "client-id-stable",
		ClientSecretHash: "old-secret-hash",
		Scopes:           "audit:read",
		Status:           "active",
	}

	updated := apiKeyWithUpdatedScopes(key, []string{"audit:read", "org-context:read"})

	assert.Equal(t, "client-id-stable", updated.ClientID)
	assert.Equal(t, "old-secret-hash", updated.ClientSecretHash)
	assert.Equal(t, "audit:read org-context:read", updated.Scopes)
}

func TestApiKeyWithRotatedSecretHashPreservesClientIDAndScopes(t *testing.T) {
	key := domain.ApiKey{
		ID:               "api-key-id",
		Name:             "M2M Test",
		ClientID:         "client-id-stable",
		ClientSecretHash: "old-secret-hash",
		Scopes:           "audit:read org-context:read",
		Status:           "active",
	}

	updated := apiKeyWithRotatedSecretHash(key, "new-secret-hash")

	assert.Equal(t, "client-id-stable", updated.ClientID)
	assert.Equal(t, "audit:read org-context:read", updated.Scopes)
	assert.Equal(t, "new-secret-hash", updated.ClientSecretHash)
}

func TestNormalizeApiKeyScopesTrimsAndDeduplicates(t *testing.T) {
	scopes := normalizeApiKeyScopes([]string{
		" audit:read ",
		"",
		"org-context:read",
		"audit:read",
	})

	assert.Equal(t, []string{"audit:read", "org-context:read"}, scopes)
}
