package rest_test

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

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/internal/interfaces/rest"
	"github.com/nexuscrm/backend/pkg/apierr"
	"github.com/nexuscrm/backend/pkg/auth"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func strPtr(s string) *string { return &s }

// ---------------------------------------------------------------------------
// GetUserFromContext
// ---------------------------------------------------------------------------

func TestGetUserFromContext_ValidUser(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/", nil)

	authUser := auth.UserSession{
		ID:        "user-123",
		Name:      "Test User",
		Email:     "test@example.com",
		ProfileId: constants.ProfileSystemAdmin,
		RoleId:    strPtr("role-1"),
	}
	c.Set(constants.ContextKeyUser, authUser)

	user := rest.GetUserFromContext(c)

	require.NotNil(t, user)
	assert.Equal(t, "user-123", user.ID)
	assert.Equal(t, "Test User", user.Name)
	assert.Equal(t, "test@example.com", *user.Email)
	assert.Equal(t, constants.ProfileSystemAdmin, user.ProfileID)
	assert.Equal(t, strPtr("role-1"), user.RoleID)
	assert.True(t, user.IsSystemAdmin)
}

func TestGetUserFromContext_NoUser(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/", nil)
	// No user set in context

	user := rest.GetUserFromContext(c)
	assert.Nil(t, user)
}

func TestGetUserFromContext_StandardUser(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/", nil)

	authUser := auth.UserSession{
		ID:        "user-456",
		Name:      "Standard User",
		Email:     "std@example.com",
		ProfileId: constants.ProfileStandardUser,
	}
	c.Set(constants.ContextKeyUser, authUser)

	user := rest.GetUserFromContext(c)

	require.NotNil(t, user)
	assert.Equal(t, "user-456", user.ID)
	assert.False(t, user.IsSystemAdmin)
}

// ---------------------------------------------------------------------------
// RequireParam
// ---------------------------------------------------------------------------

func TestRequireParam_Present(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/test/abc", nil)
	c.Params = gin.Params{{Key: "id", Value: "abc"}}

	val, ok := rest.RequireParam(c, "id")
	assert.True(t, ok)
	assert.Equal(t, "abc", val)
}

func TestRequireParam_Missing(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/test/", nil)
	c.Params = gin.Params{{Key: "id", Value: ""}}

	val, ok := rest.RequireParam(c, "id")
	assert.False(t, ok)
	assert.Equal(t, "", val)
}

// ---------------------------------------------------------------------------
// BindJSON
// ---------------------------------------------------------------------------

func TestBindJSON_ValidJSON(t *testing.T) {
	body := map[string]string{"name": "test"}
	bodyBytes, _ := json.Marshal(body)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/test", bytes.NewReader(bodyBytes))
	c.Request.Header.Set("Content-Type", "application/json")

	var target struct {
		Name string `json:"name"`
	}
	ok := rest.BindJSON(c, &target)
	assert.True(t, ok)
	assert.Equal(t, "test", target.Name)
}

func TestBindJSON_InvalidJSON(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/test", bytes.NewReader([]byte(`{invalid`)))
	c.Request.Header.Set("Content-Type", "application/json")

	var target struct {
		Name string `json:"name"`
	}
	ok := rest.BindJSON(c, &target)
	assert.False(t, ok)
}

// ---------------------------------------------------------------------------
// BindJSONStrict
// ---------------------------------------------------------------------------

func TestBindJSONStrict_ValidJSON(t *testing.T) {
	body := map[string]string{"name": "test"}
	bodyBytes, _ := json.Marshal(body)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/test", bytes.NewReader(bodyBytes))
	c.Request.Header.Set("Content-Type", "application/json")

	var target struct {
		Name string `json:"name"`
	}
	ok := rest.BindJSONStrict(c, &target)
	assert.True(t, ok)
	assert.Equal(t, "test", target.Name)
}

func TestBindJSONStrict_UnknownFields(t *testing.T) {
	body := `{"name":"test","unknown":"field"}`

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/test", bytes.NewReader([]byte(body)))
	c.Request.Header.Set("Content-Type", "application/json")

	var target struct {
		Name string `json:"name"`
	}
	ok := rest.BindJSONStrict(c, &target)
	assert.False(t, ok, "BindJSONStrict should reject unknown fields")
}

func TestBindJSONStrict_InvalidJSON(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/test", bytes.NewReader([]byte(`{bad json`)))
	c.Request.Header.Set("Content-Type", "application/json")

	var target struct {
		Name string `json:"name"`
	}
	ok := rest.BindJSONStrict(c, &target)
	assert.False(t, ok)
}

// ---------------------------------------------------------------------------
// RespondAppError — in test mode with apierr.Error
// ---------------------------------------------------------------------------

func TestRespondAppError_NonAPIError(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/", nil)

	rest.RespondAppError(c, assert.AnError)

	// Non-apierr.Error is recorded in gin context errors for middleware handling
	assert.NotEmpty(t, c.Errors)
}

func TestRespondAppError_SalesforceAPIError(t *testing.T) {
	gin.SetMode(gin.TestMode)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest(http.MethodGet, "/", nil)

	rest.RespondAppError(c, apierr.NewSalesforce(http.StatusBadRequest, apierr.SFCodeInvalidCrossReferenceKey, "layout mismatch"))

	assert.Equal(t, http.StatusBadRequest, w.Code)
	assert.Contains(t, w.Body.String(), apierr.SFCodeInvalidCrossReferenceKey)
	assert.Contains(t, w.Body.String(), "layout mismatch")
}

// ---------------------------------------------------------------------------
// GetAllFieldTypes — smoke test
// ---------------------------------------------------------------------------

func TestGetAllFieldTypes_NotEmpty(t *testing.T) {
	fieldTypes := rest.GetAllFieldTypes()
	assert.NotEmpty(t, fieldTypes, "Field types should not be empty")

	// Check basic structure
	for _, ft := range fieldTypes {
		assert.NotEmpty(t, ft.Name, "Field type name should not be empty")
		assert.NotEmpty(t, ft.Label, "Field type label should not be empty")
	}
}

// ---------------------------------------------------------------------------
// toReportResponse / toDashboardResponse — test via exported helpers
// These are unexported but exercised through handler code paths.
// We test the equivalent logic through the ListReports path.
// ---------------------------------------------------------------------------

// Verify that the UserSession → models.UserSession mapping works correctly
func TestUserSession_FieldMapping(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/", nil)

	authUser := auth.UserSession{
		ID:        "user-789",
		Name:      "Admin User",
		Email:     "admin@example.com",
		ProfileId: constants.ProfileSystemAdmin,
		RoleId:    strPtr("role-admin"),
	}
	c.Set(constants.ContextKeyUser, authUser)

	user := rest.GetUserFromContext(c)
	require.NotNil(t, user)

	// Verify all fields are correctly mapped
	expected := &models.UserSession{
		ID:            "user-789",
		Name:          "Admin User",
		ProfileID:     constants.ProfileSystemAdmin,
		RoleID:        strPtr("role-admin"),
		IsSystemAdmin: true,
	}
	assert.Equal(t, expected.ID, user.ID)
	assert.Equal(t, expected.Name, user.Name)
	assert.Equal(t, expected.ProfileID, user.ProfileID)
	assert.Equal(t, expected.RoleID, user.RoleID)
	assert.Equal(t, expected.IsSystemAdmin, user.IsSystemAdmin)
}
