package rest

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

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

type CompositeBatchHandler struct {
	router *gin.Engine // Reference to main router for internal dispatch
}

func NewCompositeBatchHandler(router *gin.Engine) *CompositeBatchHandler {
	return &CompositeBatchHandler{
		router: router,
	}
}

// HandleBatch handles POST /composite/batch
func (h *CompositeBatchHandler) HandleBatch(c *gin.Context) {
	var req composite.BatchRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "Invalid request body"))
		return
	}

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

	response := composite.BatchResponse{
		Results: make([]composite.BatchSubResult, len(req.BatchRequests)),
	}

	for i, subReq := range req.BatchRequests {
		// Create internal request
		internalReq, err := h.createInternalRequest(c, subReq)
		if err != nil {
			h.recordError(&response, i, subReq.ReferenceID, http.StatusBadRequest, err.Error())
			if req.HaltOnError {
				break
			}
			continue
		}

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

		// Parse result
		var result interface{}
		if writer.Body.Len() > 0 {
			// Try parsing as JSON
			if err := json.Unmarshal(writer.Body.Bytes(), &result); err != nil {
				// Fallback to string if not JSON
				result = writer.Body.String()
			}
		}

		response.Results[i] = composite.BatchSubResult{
			StatusCode:  writer.StatusCode,
			ReferenceID: subReq.ReferenceID,
			Result:      result,
			HttpHeaders: convertHeaders(writer.Header()),
		}

		if writer.StatusCode >= 400 {
			response.HasErrors = true
			if req.HaltOnError {
				break
			}
		}
	}

	c.JSON(http.StatusOK, response)
}

func (h *CompositeBatchHandler) createInternalRequest(c *gin.Context, subReq composite.SubRequest) (*http.Request, error) {
	// Construct URL
	// Subreq URL is relative e.g. "/services/data/v63.0/sobjects/Account"
	// Or absolute. Salesforce Composite expects strict relative paths usually.
	targetURL := subReq.URL
	if !strings.HasPrefix(targetURL, "/") {
		// Relative URL. Prepend version root.
		// Extract version from current request path or param.
		version := c.Param("version")
		if version == "" {
			version = "v63.0" // Default fallback
		}
		targetURL = "/services/data/" + version + "/" + subReq.URL
	}

	// Create Body reader
	// SubRequest.Body is json.RawMessage (already valid JSON bytes).
	// Pass directly — do NOT json.Marshal again (that would double-encode).
	var bodyReader io.Reader
	if len(subReq.Body) > 0 {
		bodyReader = bytes.NewReader(subReq.Body)
	}

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

	// Copy Headers from parent request + subReq headers
	// 1. Auth Header from original Context
	authHeader := c.GetHeader("Authorization")
	if authHeader != "" {
		req.Header.Set("Authorization", authHeader)
	}

	// 2. Content-Type default
	req.Header.Set("Content-Type", "application/json")

	// 3. SubReq Headers override
	for k, v := range subReq.HttpHeaders {
		req.Header.Set(k, v)
	}

	return req, nil
}

func (h *CompositeBatchHandler) recordError(resp *composite.BatchResponse, index int, refID string, code int, msg string) {
	resp.HasErrors = true
	resp.Results[index] = composite.BatchSubResult{
		StatusCode:  code,
		ReferenceID: refID,
		Result:      []map[string]string{{"errorCode": "INVALID_REQUEST", "message": msg}},
	}
}

// ResponseWriter captures Gin response
type ResponseWriter struct {
	gin.ResponseWriter
	Body       *bytes.Buffer
	StatusCode int
	HeaderMap  http.Header
}

func NewResponseWriter() *ResponseWriter {
	return &ResponseWriter{
		Body:       &bytes.Buffer{},
		StatusCode: 200, // Default
		HeaderMap:  make(http.Header),
	}
}

func (w *ResponseWriter) Write(b []byte) (int, error) {
	return w.Body.Write(b)
}

func (w *ResponseWriter) WriteHeader(statusCode int) {
	w.StatusCode = statusCode
}

func (w *ResponseWriter) Header() http.Header {
	return w.HeaderMap
}

func (w *ResponseWriter) WriteString(s string) (int, error) {
	return w.Body.WriteString(s)
}

func convertHeaders(h http.Header) map[string]string {
	res := make(map[string]string)
	for k, v := range h {
		if len(v) > 0 {
			res[k] = v[0]
		}
	}
	return res
}
