package rest

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

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/pkg/auth"
	pkgerrors "github.com/nexuscrm/backend/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/stretchr/testify/assert"
)

// setTestUser sets a default admin user in the gin context for testing.
func setTestUser(c *gin.Context) {
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user-001",
		Name:      "Test Admin",
		Email:     "admin@test.com",
		ProfileId: constants.ProfileSystemAdmin,
	})
}

// ---------------------------------------------------------------------------
// splitName — helper function
// ---------------------------------------------------------------------------

func TestSplitName_FirstName(t *testing.T) {
	assert.Equal(t, "John", splitName("John Doe", true))
}

func TestSplitName_LastName(t *testing.T) {
	assert.Equal(t, "Doe", splitName("John Doe", false))
}

func TestSplitName_SingleName_First(t *testing.T) {
	assert.Equal(t, "Admin", splitName("Admin", true))
}

func TestSplitName_SingleName_Last(t *testing.T) {
	assert.Equal(t, "", splitName("Admin", false))
}

func TestSplitName_MultiWordLast(t *testing.T) {
	assert.Equal(t, "van Gogh", splitName("Vincent van Gogh", false))
}

func TestSplitName_Empty(t *testing.T) {
	assert.Equal(t, "", splitName("", true))
}

func TestSplitName_Whitespace(t *testing.T) {
	assert.Equal(t, "John", splitName("  John  Doe  ", true))
}

// ---------------------------------------------------------------------------
// NewOAuthHandler
// ---------------------------------------------------------------------------

func TestNewOAuthHandler(t *testing.T) {
	handler := NewOAuthHandler(nil)
	assert.NotNil(t, handler)
}

// ---------------------------------------------------------------------------
// OAuthHandler.Authorize — validation paths (no service calls needed)
// ---------------------------------------------------------------------------

func TestOAuthHandler_Authorize_NoUser(t *testing.T) {
	handler := &OAuthHandler{}
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/services/oauth2/authorize", nil)

	assert.Panics(t, func() { handler.Authorize(c) })
}

func TestOAuthHandler_Authorize_BadResponseType(t *testing.T) {
	handler := &OAuthHandler{}
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/services/oauth2/authorize?response_type=token&client_id=abc&redirect_uri=http://localhost", nil)
	setTestUser(c)

	handler.Authorize(c)
	assert.Equal(t, http.StatusBadRequest, w.Code)
	assert.Contains(t, w.Body.String(), "unsupported_response_type")
}

// ---------------------------------------------------------------------------
// OAuthHandler.Token — validation paths
// ---------------------------------------------------------------------------

func TestOAuthHandler_Token_InvalidBody(t *testing.T) {
	handler := &OAuthHandler{}
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/services/oauth2/token", strings.NewReader("{invalid json"))
	c.Request.Header.Set("Content-Type", "application/json")

	handler.Token(c)
	assert.Equal(t, http.StatusBadRequest, w.Code)
	assert.Contains(t, w.Body.String(), "invalid_request")
}

func TestOAuthHandler_Token_UnsupportedGrantType(t *testing.T) {
	handler := &OAuthHandler{}
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	form := url.Values{}
	form.Set("grant_type", "implicit")
	c.Request = httptest.NewRequest("POST", "/services/oauth2/token", strings.NewReader(form.Encode()))
	c.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	handler.Token(c)
	assert.Equal(t, http.StatusBadRequest, w.Code)
	assert.Contains(t, w.Body.String(), "unsupported_grant_type")
}

func TestOAuthHandler_Token_PasswordGrant_MissingCredentials(t *testing.T) {
	handler := &OAuthHandler{}
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	form := url.Values{}
	form.Set("grant_type", "password")
	// Missing username and password
	c.Request = httptest.NewRequest("POST", "/services/oauth2/token", strings.NewReader(form.Encode()))
	c.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	handler.Token(c)
	assert.Equal(t, http.StatusBadRequest, w.Code)
	assert.Contains(t, w.Body.String(), "username and password are required")
}

func TestOAuthHandler_Token_ClientCredentials_MissingCreds(t *testing.T) {
	handler := &OAuthHandler{}
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	form := url.Values{}
	form.Set("grant_type", "client_credentials")
	// Missing client_id and client_secret
	c.Request = httptest.NewRequest("POST", "/services/oauth2/token", strings.NewReader(form.Encode()))
	c.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	handler.Token(c)
	assert.Equal(t, http.StatusBadRequest, w.Code)
	assert.Contains(t, w.Body.String(), "client_id and client_secret are required")
}

func TestMapConnectedAppLookupError_NotFound(t *testing.T) {
	status, payload := mapConnectedAppLookupError(pkgerrors.NewNotFoundError("connected_app", "client-1"))

	assert.Equal(t, http.StatusBadRequest, status)
	assert.Equal(t, "invalid_client", payload["error"])
	assert.Equal(t, "unknown client_id", payload["error_description"])
}

func TestMapConnectedAppLookupError_Disabled(t *testing.T) {
	status, payload := mapConnectedAppLookupError(pkgerrors.NewForbiddenError("connected app is disabled"))

	assert.Equal(t, http.StatusBadRequest, status)
	assert.Equal(t, "invalid_client", payload["error"])
	assert.Equal(t, "connected app is disabled", payload["error_description"])
}

func TestMapConnectedAppLookupError_Internal(t *testing.T) {
	status, payload := mapConnectedAppLookupError(pkgerrors.NewInternalError("db down", nil))

	assert.Equal(t, http.StatusInternalServerError, status)
	assert.Equal(t, "server_error", payload["error"])
	assert.Equal(t, "failed to load connected app", payload["error_description"])
}

func TestMapClientCredentialsSessionError_InactiveUser(t *testing.T) {
	status, payload := mapClientCredentialsSessionError(pkgerrors.NewUnauthorizedError("User account is inactive"))

	assert.Equal(t, http.StatusUnauthorized, status)
	assert.Equal(t, "invalid_grant", payload["error"])
	assert.Equal(t, "User account is inactive", payload["error_description"])
}

func TestMapClientCredentialsSessionError_LockedUser(t *testing.T) {
	status, payload := mapClientCredentialsSessionError(pkgerrors.NewUnauthorizedError("Account is temporarily locked"))

	assert.Equal(t, http.StatusUnauthorized, status)
	assert.Equal(t, "invalid_grant", payload["error"])
	assert.Equal(t, "Account is temporarily locked", payload["error_description"])
}

func TestMapClientCredentialsSessionError_Internal(t *testing.T) {
	status, payload := mapClientCredentialsSessionError(pkgerrors.NewInternalError("failed to persist session", nil))

	assert.Equal(t, http.StatusBadRequest, status)
	assert.Equal(t, "server_error", payload["error"])
	assert.Equal(t, "failed to generate token", payload["error_description"])
}

// ---------------------------------------------------------------------------
// OAuthHandler.Revoke — validation paths
// ---------------------------------------------------------------------------

func TestOAuthHandler_Revoke_EmptyToken(t *testing.T) {
	handler := &OAuthHandler{}
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/services/oauth2/revoke", nil)

	handler.Revoke(c)
	// Empty token still returns 200 (Salesforce behavior)
	assert.Equal(t, http.StatusOK, w.Code)
}

func TestOAuthHandler_Revoke_TokenFromQuery(t *testing.T) {
	handler := &OAuthHandler{}
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	// Token in query param but no svcMgr - will panic if code tries to call Logout
	// With nil svcMgr this will crash, so test only the empty case
	c.Request = httptest.NewRequest("POST", "/services/oauth2/revoke", nil)

	handler.Revoke(c)
	assert.Equal(t, http.StatusOK, w.Code)
}

// ---------------------------------------------------------------------------
// OAuthHandler.UserInfo — validation paths
// ---------------------------------------------------------------------------

func TestOAuthHandler_UserInfo_NoUser(t *testing.T) {
	handler := &OAuthHandler{}
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/services/oauth2/userinfo", nil)
	// No user in context

	assert.Panics(t, func() { handler.UserInfo(c) })
}
