package rest

import (
	"encoding/json"
	"errors"
	"net/http"
	"os"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/nexuscrm/backend/pkg/apierr"
	"github.com/nexuscrm/backend/pkg/auth"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

const defaultLocalAPIBaseURL = "http://127.0.0.1:3001"

// getAPIBaseURL returns the API base URL from environment or default
func getAPIBaseURL() string {
	if url := os.Getenv("API_BASE_URL"); url != "" {
		return url
	}
	return defaultLocalAPIBaseURL
}

// resolveInstanceURLFromRequest is the context-aware version that reads
// proxy headers (X-Forwarded-Proto, X-Forwarded-Host) for correct scheme.
func resolveInstanceURLFromRequest(c *gin.Context) string {
	if url := os.Getenv("API_BASE_URL"); url != "" {
		return url
	}

	scheme := c.GetHeader("X-Forwarded-Proto")
	if scheme == "" {
		if c.Request.TLS != nil {
			scheme = "https"
		} else {
			scheme = "http"
		}
	}

	host := c.GetHeader("X-Forwarded-Host")
	if host == "" {
		host = c.Request.Host
	}

	if host == "" || host == "localhost:3001" || host == "127.0.0.1:3001" {
		return getAPIBaseURL()
	}

	return scheme + "://" + host
}

// GetUserFromContext extracts the authenticated user from gin.Context
func GetUserFromContext(c *gin.Context) *models.UserSession {
	// ... (content skipped, targeting imports and RespondAppError)
	userInterface, exists := c.Get(constants.ContextKeyUser)
	if !exists {
		return nil
	}
	authUser := userInterface.(auth.UserSession)
	return &models.UserSession{
		ID:             authUser.ID,
		Name:           authUser.Name,
		Email:          &authUser.Email,
		ProfileID:      authUser.ProfileId,
		ProfileName:    authUser.ProfileName,
		RoleID:         authUser.RoleId,
		RoleName:       authUser.RoleName,
		IsSystemAdmin:  authUser.IsSuperUser(),
		ConnectedAppId: authUser.ConnectedAppId,
		Scope:          authUser.Scope,
	}
}

// MustGetUser extracts the authenticated user from the request context.
// Panics if user is nil — only use in routes protected by requireAuth middleware.
func MustGetUser(c *gin.Context) *models.UserSession {
	user := GetUserFromContext(c)
	if user == nil {
		panic("MustGetUser: requireAuth middleware not applied")
	}
	return user
}

// RespondAppError sends a standardised JSON error using pkg/apierr
// It delegates to the GlobalErrorHandler by appending the error to the context.
func RespondAppError(c *gin.Context, err error) {
	// In TestMode, render immediately if it's an API error, to allow unit tests to verify status
	if gin.Mode() == gin.TestMode {
		var apiErr *apierr.Error
		if errors.As(err, &apiErr) {
			c.JSON(apiErr.Status, apiErr)
			return
		}
		var sfErr *apierr.SalesforceAPIError
		if errors.As(err, &sfErr) {
			c.JSON(sfErr.Status, []apierr.SFError{sfErr.Err})
			return
		}
	}
	// Let GlobalErrorHandler middleware handle it
	c.Error(err)
}

// BindJSON binds JSON and returns true if successful. If failed, it sends bad request error.
func BindJSON(c *gin.Context, obj interface{}) bool {
	if err := c.ShouldBindJSON(obj); err != nil {
		RespondAppError(c, apierr.Invalid(err.Error()))
		return false
	}
	return true
}

// BindJSONStrict binds JSON and enforces strict field validation (no unknown fields).
func BindJSONStrict(c *gin.Context, obj interface{}) bool {
	dec := json.NewDecoder(c.Request.Body)
	dec.DisallowUnknownFields()
	if err := dec.Decode(obj); err != nil {
		RespondAppError(c, apierr.Invalid("Invalid JSON: "+err.Error()))
		return false
	}
	if err := binding.Validator.ValidateStruct(obj); err != nil {
		RespondAppError(c, apierr.ValidationFailed(map[string]interface{}{"error": err.Error()}))
		return false
	}
	return true
}

// RequireParam validates that a URL parameter is not empty. Returns the value if valid, or empty string and sends error.
// Responds with a Salesforce-compatible error array using REQUIRED_FIELD_MISSING.
func RequireParam(c *gin.Context, paramName string) (string, bool) {
	value := c.Param(paramName)
	if value == "" {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(
			apierr.SFCodeRequiredFieldMissing, paramName+" is required"))
		return "", false
	}
	return value, true
}
