package rest

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"regexp"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/internal/features/composite"
	"github.com/nexuscrm/backend/pkg/apierr"
)

// referencePattern matches @{referenceId.field} or @{referenceId.field.nested}
var referencePattern = regexp.MustCompile(`@\{([^}]+)\}`)

// CompositeHandler handles POST /composite.
// Uses generic router dispatch (router.ServeHTTP) to execute any REST API subrequest,
// with @{refId.field} reference resolution between sequential subrequests.
type CompositeHandler struct {
	router *gin.Engine
}

// NewCompositeHandler creates a handler with access to the router for internal dispatch.
func NewCompositeHandler(router *gin.Engine) *CompositeHandler {
	return &CompositeHandler{router: router}
}

// HandleComposite handles POST /composite
// Salesforce Composite API — executes subrequests sequentially with reference chaining.
func (h *CompositeHandler) HandleComposite(c *gin.Context) {
	var req composite.CompositeRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "Invalid request body"))
		return
	}

	if len(req.CompositeRequest) > 25 {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeLimitExceeded, "Maximum 25 subrequests allowed"))
		return
	}

	// Store results by referenceId for @{refId.field} resolution
	resultsByRef := make(map[string]interface{})
	compositeResponse := make([]composite.CompositeSubResult, 0, len(req.CompositeRequest))

	for _, subReq := range req.CompositeRequest {
		// Guard: reject self-referencing subrequests to prevent infinite recursion.
		// Salesforce also blocks composite-within-composite.
		if isCompositeURL(subReq.URL) {
			subResult := composite.CompositeSubResult{
				HttpStatusCode: http.StatusBadRequest,
				ReferenceId:    subReq.ReferenceId,
				Body: []map[string]interface{}{
					{"errorCode": "INVALID_REQUEST", "message": "Composite subrequest cannot target composite endpoints"},
				},
				HttpHeaders: map[string]string{},
			}
			compositeResponse = append(compositeResponse, subResult)
			if req.AllOrNone {
				break
			}
			continue
		}

		// Resolve @{refId.field} references in URL and body
		resolvedURL := resolveReferences(subReq.URL, resultsByRef)
		var resolvedBody json.RawMessage
		if len(subReq.Body) > 0 {
			resolvedBody = json.RawMessage(resolveReferences(string(subReq.Body), resultsByRef))
		}

		// Create internal HTTP request
		internalReq, err := h.createInternalRequest(c, subReq.Method, resolvedURL, resolvedBody, subReq.HttpHeaders)
		if err != nil {
			subResult := composite.CompositeSubResult{
				HttpStatusCode: http.StatusBadRequest,
				ReferenceId:    subReq.ReferenceId,
				Body: []map[string]interface{}{
					{"errorCode": "INVALID_REQUEST", "message": err.Error()},
				},
				HttpHeaders: map[string]string{},
			}
			compositeResponse = append(compositeResponse, subResult)

			if req.AllOrNone {
				break
			}
			continue
		}

		// Dispatch through Gin's router
		writer := NewResponseWriter()
		h.router.ServeHTTP(writer, internalReq)

		// Parse response body
		var body interface{}
		if writer.Body.Len() > 0 {
			if err := json.Unmarshal(writer.Body.Bytes(), &body); err != nil {
				body = writer.Body.String()
			}
		}

		subResult := composite.CompositeSubResult{
			HttpStatusCode: writer.StatusCode,
			ReferenceId:    subReq.ReferenceId,
			Body:           body,
			HttpHeaders:    convertHeaders(writer.Header()),
		}
		compositeResponse = append(compositeResponse, subResult)

		// Store result by referenceId for later @{refId} resolution
		if subReq.ReferenceId != "" {
			resultsByRef[subReq.ReferenceId] = body
		}

		// allOrNone: stop on first error
		if writer.StatusCode >= 400 && req.AllOrNone {
			break
		}
	}

	c.JSON(http.StatusOK, composite.CompositeResponse{
		CompositeResponse: compositeResponse,
	})
}

// createInternalRequest builds an http.Request for internal router dispatch.
func (h *CompositeHandler) createInternalRequest(
	c *gin.Context,
	method string,
	targetURL string,
	body json.RawMessage,
	headers map[string]string,
) (*http.Request, error) {
	// Ensure URL starts with /
	if !strings.HasPrefix(targetURL, "/") {
		version := c.Param("version")
		if version == "" {
			version = "v63.0"
		}
		targetURL = "/services/data/" + version + "/" + targetURL
	}

	var bodyReader io.Reader
	if len(body) > 0 {
		bodyReader = bytes.NewReader(body)
	}

	req, err := http.NewRequest(method, targetURL, bodyReader)
	if err != nil {
		return nil, err
	}

	// Forward auth and CSRF-relevant headers from parent request.
	// The parent request already passed CSRF validation, so internal
	// sub-requests must carry the same Origin/Referer to avoid being
	// rejected by the CSRF middleware.
	if auth := c.GetHeader("Authorization"); auth != "" {
		req.Header.Set("Authorization", auth)
	}
	if origin := c.GetHeader("Origin"); origin != "" {
		req.Header.Set("Origin", origin)
	}
	if referer := c.GetHeader("Referer"); referer != "" {
		req.Header.Set("Referer", referer)
	}
	if csrf := c.GetHeader("X-CSRF-Token"); csrf != "" {
		req.Header.Set("X-CSRF-Token", csrf)
	}
	req.Header.Set("Content-Type", "application/json")

	// Apply subrequest-specific headers
	for k, v := range headers {
		req.Header.Set(k, v)
	}

	return req, nil
}

// resolveReferences replaces @{referenceId.field} patterns with values from previous results.
// Supports dot-notation for nested fields: @{NewAccount.id}, @{Describe.fields[0].name}
func resolveReferences(input string, results map[string]interface{}) string {
	if len(results) == 0 || !strings.Contains(input, "@{") {
		return input
	}

	return referencePattern.ReplaceAllStringFunc(input, func(match string) string {
		// Extract "referenceId.field" from @{referenceId.field}
		inner := match[2 : len(match)-1] // strip @{ and }
		parts := strings.SplitN(inner, ".", 2)
		if len(parts) < 2 {
			return match // No field path, return as-is
		}

		refId := parts[0]
		fieldPath := parts[1]

		result, ok := results[refId]
		if !ok {
			return match // Reference not found, return as-is
		}

		value := extractField(result, fieldPath)
		if value == nil {
			return match // Field not found, return as-is
		}

		// Convert to string representation
		switch v := value.(type) {
		case string:
			return v
		case float64:
			if v == float64(int64(v)) {
				return fmt.Sprintf("%d", int64(v))
			}
			return fmt.Sprintf("%v", v)
		case bool:
			return fmt.Sprintf("%v", v)
		default:
			// For complex types, JSON-encode
			b, err := json.Marshal(v)
			if err != nil {
				return match
			}
			return string(b)
		}
	})
}

// extractField traverses a nested map/slice structure using dot-notation field path.
func extractField(data interface{}, path string) interface{} {
	parts := strings.Split(path, ".")
	current := data

	for _, part := range parts {
		switch v := current.(type) {
		case map[string]interface{}:
			val, ok := v[part]
			if !ok {
				return nil
			}
			current = val
		default:
			return nil
		}
	}

	return current
}

// isCompositeURL checks if a subrequest URL targets a composite endpoint,
// which would cause infinite recursion.
func isCompositeURL(url string) bool {
	// Normalize: strip query string
	path := url
	if idx := strings.Index(path, "?"); idx >= 0 {
		path = path[:idx]
	}
	path = strings.TrimRight(path, "/")

	// Block /composite, /composite/batch, /composite/graph
	// Allow /composite/sobjects and /composite/tree (those are collections, not composite dispatch)
	if strings.HasSuffix(path, "/composite") || strings.HasSuffix(path, "/composite/batch") || strings.HasSuffix(path, "/composite/graph") {
		return true
	}
	return false
}
