package main

import (
	"encoding/json"
	"fmt"
	"slices"
	"strconv"

	"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
	"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
	"github.com/higress-group/wasm-go/pkg/log"
	"github.com/higress-group/wasm-go/pkg/mcp"
	"github.com/higress-group/wasm-go/pkg/mcp/utils"
	"github.com/higress-group/wasm-go/pkg/wrapper"
	"github.com/tidwall/gjson"
)

func main() {}

const (
	JsonRpcId        = "x-envoy-jsonrpc-id"
	JsonRpcMethod    = "x-envoy-jsonrpc-method"
	JsonRpcParams    = "x-envoy-jsonrpc-params"
	JsonRpcResult    = "x-envoy-jsonrpc-result"
	JsonRpcError     = "x-envoy-jsonrpc-error"
	McpToolName      = "x-envoy-mcp-tool-name"
	McpToolArguments = "x-envoy-mcp-tool-arguments"
	McpToolResponse  = "x-envoy-mcp-tool-response"
	McpToolError     = "x-envoy-mcp-tool-error"

	DefaultMaxHeaderLength = 4000         // default max length for truncation
	MethodToolList         = "tools/list" // default method for tool list
	MethodToolCall         = "tools/call" // default method for tool call
)

type ProcessStage string

const (
	ProcessRequest  ProcessStage = "request"
	ProcessResponse ProcessStage = "response"
)

type McpConverterConfig struct {
	Stage           ProcessStage `json:"stage"`
	MaxHeaderLength int          `json:"max_header_length,omitempty"`
	AllowedMethods  []string     `json:"allowed_methods,omitempty"` // optional, for future use
}

func init() {
	mcp.LoadMCPFilter(
		mcp.FilterName("jsonrpc-converter"),
		mcp.SetConfigParser(parseConfig),
		mcp.SetJsonRpcRequestFilter(processJsonRpcRequest),
		mcp.SetJsonRpcResponseFilter(processJsonRpcResponse),
		mcp.SetToolListResponseFilter(processToolListResponse),
		mcp.SetToolCallRequestFilter(processToolCallRequest),
		mcp.SetToolCallResponseFilter(processToolCallResponse),
	)
	mcp.InitMCPFilter()
}

func parseConfig(configBytes []byte, filterConfig *any) error {
	var config McpConverterConfig
	if err := json.Unmarshal(configBytes, &config); err != nil {
		return fmt.Errorf("failed to parse mcp-converter config: %v", err)
	}
	// validate stage
	if config.Stage != ProcessRequest && config.Stage != ProcessResponse {
		return fmt.Errorf("invalid mcp-converter stage: %s, must be 'request' or 'response'", config.Stage)
	}
	// validate length
	if config.MaxHeaderLength <= 0 {
		config.MaxHeaderLength = DefaultMaxHeaderLength
	}
	// validate allowed methods
	if len(config.AllowedMethods) == 0 {
		config.AllowedMethods = []string{MethodToolList, MethodToolCall}
	}
	log.Infof("MCP Converter config parsed successfully, stage: %s", config.Stage)
	*filterConfig = config
	return nil
}

func isPreRequestStage(config any) bool {
	return config.(McpConverterConfig).Stage == ProcessRequest
}

func isPreResponseStage(config any) bool {
	return config.(McpConverterConfig).Stage == ProcessResponse
}

func isMethodAllowed(config any, method string) bool {
	allowedMethods := config.(McpConverterConfig).AllowedMethods
	return slices.Contains(allowedMethods, method)
}

// Remove jsonrpc headers
func removeJsonRpcHeaders(isRequest bool) {
	headersToRemove := []string{
		JsonRpcId,
		JsonRpcMethod,
		JsonRpcParams,
		JsonRpcResult,
		McpToolName,
		McpToolArguments,
		McpToolResponse,
		McpToolError,
	}
	for _, header := range headersToRemove {
		var err error
		if isRequest {
			err = proxywasm.RemoveHttpRequestHeader(header)
		} else {
			err = proxywasm.RemoveHttpResponseHeader(header)
		}
		if err != nil {
			log.Errorf("failed to remove header %s: %v", header, err)
		}
	}
}

// Insert jsonrpc headers
func insertJsonRpcHeaders(isRequest bool, config any, name string, value string) {
	if value == "" {
		log.Debugf("Skipping insertion of empty header %s", name)
		return
	}
	truncatedValue := truncateString(value, config)
	var err error
	if isRequest {
		err = proxywasm.ReplaceHttpRequestHeader(name, truncatedValue)
	} else {
		err = proxywasm.ReplaceHttpResponseHeader(name, truncatedValue)
	}
	if err != nil {
		log.Errorf("failed to insert header %s: %v", name, err)
	}
}

func printHeaders(stage ProcessStage, s string) {
	var err error
	var headersNow any
	switch stage {
	case ProcessRequest:
		headersNow, err = proxywasm.GetHttpRequestHeaders()
	case ProcessResponse:
		headersNow, err = proxywasm.GetHttpResponseHeaders()
	}
	if err != nil {
		log.Errorf("PrintHeaders %s: failed to get request headers: %v", s, err)
		return
	}
	log.Debugf("PrintHeaders %s: %v", s, headersNow)
}

// truncates a string to a maximum length of 4000 characters.
func truncateString(s string, config any) string {
	length := config.(McpConverterConfig).MaxHeaderLength
	if len(s) <= length {
		return s
	}
	prefix := s[:length/2]
	suffix := s[len(s)-length/2:]

	return fmt.Sprintf("%s...(truncated)...%s", prefix, suffix)
}

func processJsonRpcRequest(context wrapper.HttpContext, config any, id utils.JsonRpcID, method string, params gjson.Result, rawBody []byte) types.Action {
	if isPreResponseStage(config) {
		// pre-response removes request headers, which are added by pre-request
		removeJsonRpcHeaders(true)
		return types.ActionContinue
	}

	if !isMethodAllowed(config, method) {
		log.Debugf("[JsonRpcRequest] Method %s is not allowed, skipping processing", method)
		return types.ActionContinue
	}

	// Set common headers, JsonRpcId, JsonRpcMethod
	insertJsonRpcHeaders(true, config, JsonRpcId, id.StringValue)
	insertJsonRpcHeaders(true, config, JsonRpcMethod, method)

	// Set other headers based on the method
	// For MethodToolCall, we set the params in processToolCallRequest
	if method != MethodToolCall {
		// JsonRpcParams
		insertJsonRpcHeaders(true, config, JsonRpcParams, params.String())
	}

	return types.ActionContinue
}

func processJsonRpcResponse(context wrapper.HttpContext, config any, id utils.JsonRpcID, result, error gjson.Result, rawBody []byte) types.Action {
	if isPreRequestStage(config) {
		// pre-request removes response headers, which are added by pre-response
		removeJsonRpcHeaders(false)
		return types.ActionContinue
	}

	method := context.GetStringContext("JSONRPC_METHOD", "")
	if !isMethodAllowed(config, method) {
		log.Debugf("[JsonRpcResponse] Method %s is not allowed, skipping processing", method)
		return types.ActionContinue
	}

	// Set common headers, JsonRpcId, JsonRpcMethod
	insertJsonRpcHeaders(false, config, JsonRpcId, id.StringValue)
	insertJsonRpcHeaders(false, config, JsonRpcMethod, method)

	// Set other headers based on the method
	// For MethodToolList & MethodToolCall, we set the params in processToolCallResponse and processToolListResponse
	if method != MethodToolList && method != MethodToolCall {
		// JsonRpcResult
		insertJsonRpcHeaders(false, config, JsonRpcResult, result.String())
		// JsonRpcError
		insertJsonRpcHeaders(false, config, JsonRpcError, error.String())
	}

	return types.ActionContinue
}

func processToolListResponse(ctx wrapper.HttpContext, config any, tools gjson.Result, rawBody []byte) types.Action {
	if isPreRequestStage(config) {
		return types.ActionContinue
	}

	if !isMethodAllowed(config, MethodToolList) {
		log.Debugf("[ToolListResponse] Method %s is not allowed, skipping processing", MethodToolList)
		return types.ActionContinue
	}

	// JsonRpcResult
	insertJsonRpcHeaders(false, config, JsonRpcResult, tools.String())

	return types.ActionContinue
}

func processToolCallRequest(ctx wrapper.HttpContext, config any, toolName string, toolArgs gjson.Result, rawBody []byte) types.Action {
	if isPreResponseStage(config) {
		return types.ActionContinue
	}

	if !isMethodAllowed(config, MethodToolCall) {
		log.Debugf("[ToolCallRequest] Method %s is not allowed, skipping processing", MethodToolCall)
		return types.ActionContinue
	}

	// McpToolName, McpToolArguments
	insertJsonRpcHeaders(true, config, McpToolName, toolName)
	insertJsonRpcHeaders(true, config, McpToolArguments, toolArgs.String())

	return types.ActionContinue
}

func processToolCallResponse(ctx wrapper.HttpContext, config any, isError bool, content gjson.Result, rawBody []byte) types.Action {
	if isPreRequestStage(config) {
		return types.ActionContinue
	}

	if !isMethodAllowed(config, MethodToolCall) {
		log.Debugf("[ToolCallResponse] Method %s is not allowed, skipping processing", MethodToolCall)
		return types.ActionContinue
	}

	// McpToolResponse, McpToolError
	insertJsonRpcHeaders(false, config, McpToolResponse, content.String())
	insertJsonRpcHeaders(false, config, McpToolError, strconv.FormatBool(isError))

	return types.ActionContinue
}
