package controllers

import (
	"encoding/json"
	"log"

	orpcmodels "git.threefold.info/herocode/heroagent/pkg/openrpc/models"
	uimodels "git.threefold.info/herocode/heroagent/pkg/servers/ui/models"
	"github.com/gofiber/fiber/v2"
)

// OpenRPCController handles requests related to OpenRPC specifications
type OpenRPCController struct {
	openrpcManager uimodels.OpenRPCUIManager
}

// NewOpenRPCController creates a new instance of OpenRPCController
func NewOpenRPCController(openrpcManager uimodels.OpenRPCUIManager) *OpenRPCController {
	return &OpenRPCController{
		openrpcManager: openrpcManager,
	}
}

// OpenRPCPageData represents the data needed for the OpenRPC UI pages
type OpenRPCPageData struct {
	Title          string
	Specs          []string
	SelectedSpec   string
	Methods        []string
	SelectedMethod string
	Method         *orpcmodels.Method
	SocketPath     string
	ExampleParams  string
	Result         string
	Error          string
}

// ShowOpenRPCUI renders the OpenRPC UI page
func (c *OpenRPCController) ShowOpenRPCUI(ctx *fiber.Ctx) error {
	// Get query parameters
	selectedSpec := ctx.Query("spec", "")
	selectedMethod := ctx.Query("method", "")
	socketPath := ctx.Query("socketPath", "")

	// Get all specs
	specs := c.openrpcManager.ListSpecs()

	// Initialize page data using fiber.Map instead of struct
	pageData := fiber.Map{
		"Title":        "OpenRPC UI",
		"SpecList":     specs,
		"SelectedSpec": selectedSpec,
		"SocketPath":   socketPath,
	}

	// If a spec is selected, get its methods
	if selectedSpec != "" {
		methods := c.openrpcManager.ListMethods(selectedSpec)
		pageData["Methods"] = methods
		pageData["SelectedMethod"] = selectedMethod

		// If a method is selected, get its details
		if selectedMethod != "" {
			method := c.openrpcManager.GetMethod(selectedSpec, selectedMethod)
			if method != nil {
				pageData["Method"] = method

				// Generate example parameters if available
				if len(method.Examples) > 0 {
					exampleParams, err := json.MarshalIndent(method.Examples[0].Params, "", "  ")
					if err == nil {
						pageData["ExampleParams"] = string(exampleParams)
					}
				} else if len(method.Params) > 0 {
					// Generate example from parameter schema
					exampleParams := generateExampleParams(method.Params)
					jsonParams, err := json.MarshalIndent(exampleParams, "", "  ")
					if err == nil {
						pageData["ExampleParams"] = string(jsonParams)
					}
				}
			}
		}
	}

	return ctx.Render("pages/rpcui", pageData)
}

// ExecuteRPC handles RPC execution requests
func (c *OpenRPCController) ExecuteRPC(ctx *fiber.Ctx) error {
	// Parse request
	var request struct {
		Spec       string          `json:"spec"`
		Method     string          `json:"method"`
		SocketPath string          `json:"socketPath"`
		Params     json.RawMessage `json:"params"`
	}

	if err := ctx.BodyParser(&request); err != nil {
		return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid request: " + err.Error(),
		})
	}

	// Validate request
	if request.Spec == "" || request.Method == "" || request.SocketPath == "" {
		return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Missing required fields: spec, method, or socketPath",
		})
	}

	// Parse params
	var params interface{}
	if len(request.Params) > 0 {
		if err := json.Unmarshal(request.Params, &params); err != nil {
			return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{
				"error": "Invalid parameters: " + err.Error(),
			})
		}
	}

	// Execute RPC
	result, err := c.openrpcManager.ExecuteRPC(request.Spec, request.Method, request.SocketPath, params)
	if err != nil {
		log.Printf("Error executing RPC: %v", err)
		return ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": err.Error(),
		})
	}

	// Return result
	return ctx.JSON(fiber.Map{
		"result": result,
	})
}

// generateExampleParams generates example parameters from parameter schemas
func generateExampleParams(params []orpcmodels.Parameter) map[string]interface{} {
	example := make(map[string]interface{})

	for _, param := range params {
		example[param.Name] = generateExampleValue(param.Schema)
	}

	return example
}

// generateExampleValue generates an example value from a schema
func generateExampleValue(schema orpcmodels.SchemaObject) interface{} {
	switch schema.Type {
	case "string":
		return "example"
	case "number":
		return 0
	case "integer":
		return 0
	case "boolean":
		return false
	case "array":
		if schema.Items != nil {
			return []interface{}{generateExampleValue(*schema.Items)}
		}
		return []interface{}{}
	case "object":
		obj := make(map[string]interface{})
		for name, propSchema := range schema.Properties {
			obj[name] = generateExampleValue(propSchema)
		}
		return obj
	default:
		return nil
	}
}
