package dialectsfcrm

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/textproto"
	"regexp"
	"strings"

	"github.com/nexuscrm/dialect-sf-crm/wire"
	"github.com/nexuscrm/shared/pkg/dialect"
)

var compositeReferencePattern = regexp.MustCompile(`@\{([^}]+)\}`)

type compositeSubRequest struct {
	Method      string            `json:"method"`
	URL         string            `json:"url"`
	ReferenceID string            `json:"referenceId,omitempty"`
	Body        json.RawMessage   `json:"body,omitempty"`
	HttpHeaders map[string]string `json:"httpHeaders,omitempty"`
}

type compositeBatchRequest struct {
	BatchRequests []compositeSubRequest `json:"batchRequests"`
	HaltOnError   bool                  `json:"haltOnError,omitempty"`
}

type compositeBatchSubResult struct {
	StatusCode  int               `json:"statusCode"`
	ReferenceID string            `json:"referenceId,omitempty"`
	Result      interface{}       `json:"result"`
	HttpHeaders map[string]string `json:"httpHeaders,omitempty"`
}

type compositeBatchResponse struct {
	HasErrors bool                      `json:"hasErrors"`
	Results   []compositeBatchSubResult `json:"results"`
}

type compositeSubrequest struct {
	Body        json.RawMessage   `json:"body,omitempty"`
	Method      string            `json:"method"`
	URL         string            `json:"url"`
	ReferenceId string            `json:"referenceId"`
	HttpHeaders map[string]string `json:"httpHeaders,omitempty"`
}

type compositeRequest struct {
	AllOrNone        bool                  `json:"allOrNone"`
	CompositeRequest []compositeSubrequest `json:"compositeRequest"`
}

type compositeSubResult struct {
	Body           interface{}       `json:"body"`
	HttpHeaders    map[string]string `json:"httpHeaders,omitempty"`
	HttpStatusCode int               `json:"httpStatusCode"`
	ReferenceId    string            `json:"referenceId"`
}

type compositeResponse struct {
	CompositeResponse []compositeSubResult `json:"compositeResponse"`
}

// CompositeNeutralHandler handles POST /composite through the gin-free door.
func CompositeNeutralHandler(ctx context.Context, req *dialect.Request, deps dialect.Deps) (*dialect.Response, error) {
	if deps.Dispatch == nil {
		return internalErrorResponse("dispatch contract unavailable"), nil
	}

	var body compositeRequest
	if err := json.Unmarshal(req.Body, &body); err != nil {
		return &dialect.Response{Status: http.StatusBadRequest, Body: wire.SFErrorResponse(wire.SFCodeInvalidRequest, "Invalid request body")}, nil
	}
	if len(body.CompositeRequest) > 25 {
		return &dialect.Response{Status: http.StatusBadRequest, Body: wire.SFErrorResponse(wire.SFCodeLimitExceeded, "Maximum 25 subrequests allowed")}, nil
	}

	resultsByRef := make(map[string]interface{})
	subResponses := make([]compositeSubResult, 0, len(body.CompositeRequest))
	for _, subReq := range body.CompositeRequest {
		if isCompositeDispatchURL(subReq.URL) {
			subResponses = append(subResponses, compositeSubResult{
				HttpStatusCode: http.StatusBadRequest,
				ReferenceId:    subReq.ReferenceId,
				Body:           wire.SFErrorResponse(wire.SFCodeInvalidRequest, "Composite subrequest cannot target composite endpoints"),
				HttpHeaders:    map[string]string{},
			})
			if body.AllOrNone {
				break
			}
			continue
		}

		resolvedURL := resolveCompositeReferences(subReq.URL, resultsByRef)
		var resolvedBody json.RawMessage
		if len(subReq.Body) > 0 {
			resolvedBody = json.RawMessage(resolveCompositeReferences(string(subReq.Body), resultsByRef))
		}

		dispatchReq := buildCompositeDispatchRequest(req, subReq.Method, resolvedURL, resolvedBody, subReq.HttpHeaders, compositeHeaderForwardFull)
		dispatchResp, err := deps.Dispatch(ctx, dispatchReq)
		if err != nil {
			subResponses = append(subResponses, compositeInvalidRequestSubResult(subReq.ReferenceId, err.Error()))
			if body.AllOrNone {
				break
			}
			continue
		}

		resultBody := parseCompositeDispatchBody(dispatchResp.Body)
		subResponses = append(subResponses, compositeSubResult{
			HttpStatusCode: dispatchResp.Status,
			ReferenceId:    subReq.ReferenceId,
			Body:           resultBody,
			HttpHeaders:    dispatchResp.Header,
		})
		if subReq.ReferenceId != "" {
			resultsByRef[subReq.ReferenceId] = resultBody
		}
		if dispatchResp.Status >= 400 && body.AllOrNone {
			break
		}
	}

	return &dialect.Response{Status: http.StatusOK, Body: compositeResponse{CompositeResponse: subResponses}}, nil
}

// CompositeBatchNeutralHandler handles POST /composite/batch through the gin-free door.
func CompositeBatchNeutralHandler(ctx context.Context, req *dialect.Request, deps dialect.Deps) (*dialect.Response, error) {
	if deps.Dispatch == nil {
		return internalErrorResponse("dispatch contract unavailable"), nil
	}

	var body compositeBatchRequest
	if err := json.Unmarshal(req.Body, &body); err != nil {
		return &dialect.Response{Status: http.StatusBadRequest, Body: wire.SFErrorResponse(wire.SFCodeInvalidRequest, "Invalid request body")}, nil
	}
	if len(body.BatchRequests) > 25 {
		return &dialect.Response{Status: http.StatusBadRequest, Body: wire.SFErrorResponse(wire.SFCodeLimitExceeded, "Maximum 25 subrequests allowed")}, nil
	}

	response := compositeBatchResponse{Results: make([]compositeBatchSubResult, len(body.BatchRequests))}
	for i, subReq := range body.BatchRequests {
		dispatchReq := buildCompositeDispatchRequest(req, subReq.Method, subReq.URL, subReq.Body, subReq.HttpHeaders, compositeHeaderForwardAuthOnly)
		dispatchResp, err := deps.Dispatch(ctx, dispatchReq)
		if err != nil {
			recordCompositeBatchError(&response, i, subReq.ReferenceID, http.StatusBadRequest, err.Error())
			if body.HaltOnError {
				break
			}
			continue
		}

		response.Results[i] = compositeBatchSubResult{
			StatusCode:  dispatchResp.Status,
			ReferenceID: subReq.ReferenceID,
			Result:      parseCompositeDispatchBody(dispatchResp.Body),
			HttpHeaders: dispatchResp.Header,
		}
		if dispatchResp.Status >= 400 {
			response.HasErrors = true
			if body.HaltOnError {
				break
			}
		}
	}

	return &dialect.Response{Status: http.StatusOK, Body: response}, nil
}

type compositeHeaderForwardMode int

const (
	compositeHeaderForwardAuthOnly compositeHeaderForwardMode = iota
	compositeHeaderForwardFull
)

func buildCompositeDispatchRequest(req *dialect.Request, method string, targetURL string, body json.RawMessage, headers map[string]string, mode compositeHeaderForwardMode) dialect.DispatchRequest {
	if !strings.HasPrefix(targetURL, "/") {
		targetURL = "/services/data/" + apiVersionOrDefault(req.Version) + "/" + targetURL
	}

	out := dialect.DispatchRequest{
		Method: strings.TrimSpace(method),
		Path:   targetURL,
		Header: map[string]string{},
	}
	if len(body) > 0 {
		out.Body = []byte(body)
	}
	if auth := firstHeader(req.Header, "Authorization"); auth != "" {
		setCompositeDispatchHeader(out.Header, "Authorization", auth)
	}
	if mode == compositeHeaderForwardFull {
		if origin := firstHeader(req.Header, "Origin"); origin != "" {
			setCompositeDispatchHeader(out.Header, "Origin", origin)
		}
		if referer := firstHeader(req.Header, "Referer"); referer != "" {
			setCompositeDispatchHeader(out.Header, "Referer", referer)
		}
		if csrf := firstHeader(req.Header, "X-CSRF-Token"); csrf != "" {
			setCompositeDispatchHeader(out.Header, "X-CSRF-Token", csrf)
		}
	}
	setCompositeDispatchHeader(out.Header, "Content-Type", "application/json")
	for k, v := range headers {
		setCompositeDispatchHeader(out.Header, k, v)
	}
	return out
}

func setCompositeDispatchHeader(headers map[string]string, key, value string) {
	key = textproto.CanonicalMIMEHeaderKey(strings.TrimSpace(key))
	if key == "" {
		return
	}
	headers[key] = value
}

func firstHeader(headers map[string][]string, key string) string {
	for k, values := range headers {
		if strings.EqualFold(k, key) && len(values) > 0 {
			return values[0]
		}
	}
	return ""
}

func parseCompositeDispatchBody(body []byte) interface{} {
	if len(body) == 0 {
		return nil
	}
	var parsed interface{}
	if err := json.Unmarshal(body, &parsed); err != nil {
		return string(body)
	}
	return parsed
}

func compositeInvalidRequestSubResult(referenceID, message string) compositeSubResult {
	return compositeSubResult{
		HttpStatusCode: http.StatusBadRequest,
		ReferenceId:    referenceID,
		Body:           wire.SFErrorResponse(wire.SFCodeInvalidRequest, message),
		HttpHeaders:    map[string]string{},
	}
}

func recordCompositeBatchError(resp *compositeBatchResponse, index int, refID string, code int, msg string) {
	resp.HasErrors = true
	resp.Results[index] = compositeBatchSubResult{
		StatusCode:  code,
		ReferenceID: refID,
		Result:      wire.SFErrorResponse(wire.SFCodeInvalidRequest, msg),
	}
}

func resolveCompositeReferences(input string, results map[string]interface{}) string {
	if len(results) == 0 || !strings.Contains(input, "@{") {
		return input
	}
	return compositeReferencePattern.ReplaceAllStringFunc(input, func(match string) string {
		inner := match[2 : len(match)-1]
		parts := strings.SplitN(inner, ".", 2)
		if len(parts) < 2 {
			return match
		}
		result, ok := results[parts[0]]
		if !ok {
			return match
		}
		value := extractCompositeField(result, parts[1])
		if value == nil {
			return match
		}
		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:
			b, err := json.Marshal(v)
			if err != nil {
				return match
			}
			return string(b)
		}
	})
}

func extractCompositeField(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
}

func isCompositeDispatchURL(rawURL string) bool {
	path := rawURL
	if idx := strings.Index(path, "?"); idx >= 0 {
		path = path[:idx]
	}
	path = strings.TrimRight(path, "/")
	return strings.HasSuffix(path, "/composite") ||
		strings.HasSuffix(path, "/composite/batch") ||
		strings.HasSuffix(path, "/composite/graph")
}
