package models

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"time"

	"git.threefold.info/herocode/heroagent/pkg/openrpc"
	"git.threefold.info/herocode/heroagent/pkg/openrpc/models"
)

// OpenRPCUIManager is the interface for managing OpenRPC specifications in the UI
type OpenRPCUIManager interface {
	// ListSpecs returns a list of all loaded specification names
	ListSpecs() []string

	// GetSpec returns an OpenRPC specification by name
	GetSpec(name string) *models.OpenRPCSpec

	// ListMethods returns a list of all method names in a specification
	ListMethods(specName string) []string

	// GetMethod returns a method from a specification
	GetMethod(specName, methodName string) *models.Method

	// ExecuteRPC executes an RPC call
	ExecuteRPC(specName, methodName, socketPath string, params interface{}) (interface{}, error)
}

// OpenRPCManager implements the OpenRPCUIManager interface
type OpenRPCManager struct {
	manager *openrpc.ORPCManager
}

// NewOpenRPCManager creates a new OpenRPCUIManager
func NewOpenRPCManager() OpenRPCUIManager {
	manager := openrpc.NewORPCManager()

	// Try to load specs from the default directory
	specDirs := []string{
		"./pkg/openrpc/services",
		"./pkg/openrpc/specs",
		"./specs/openrpc",
	}

	for _, dir := range specDirs {
		err := manager.LoadSpecs(dir)
		if err == nil {
			// Successfully loaded specs from this directory
			break
		}
	}

	return &OpenRPCManager{
		manager: manager,
	}
}

// ListSpecs returns a list of all loaded specification names
func (m *OpenRPCManager) ListSpecs() []string {
	return m.manager.ListSpecs()
}

// GetSpec returns an OpenRPC specification by name
func (m *OpenRPCManager) GetSpec(name string) *models.OpenRPCSpec {
	return m.manager.GetSpec(name)
}

// ListMethods returns a list of all method names in a specification
func (m *OpenRPCManager) ListMethods(specName string) []string {
	return m.manager.ListMethods(specName)
}

// GetMethod returns a method from a specification
func (m *OpenRPCManager) GetMethod(specName, methodName string) *models.Method {
	return m.manager.GetMethod(specName, methodName)
}

// ExecuteRPC executes an RPC call
func (m *OpenRPCManager) ExecuteRPC(specName, methodName, socketPath string, params interface{}) (interface{}, error) {
	// Create JSON-RPC request
	request := map[string]interface{}{
		"jsonrpc": "2.0",
		"method":  methodName,
		"params":  params,
		"id":      1,
	}

	// Marshal request to JSON
	requestJSON, err := json.Marshal(request)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal request: %w", err)
	}

	// Check if socket path is a Unix socket or HTTP endpoint
	if socketPath[:1] == "/" {
		// Unix socket
		return executeUnixSocketRPC(socketPath, requestJSON)
	} else {
		// HTTP endpoint
		return executeHTTPRPC(socketPath, requestJSON)
	}
}

// executeUnixSocketRPC executes an RPC call over a Unix socket
func executeUnixSocketRPC(socketPath string, requestJSON []byte) (interface{}, error) {
	// Connect to Unix socket
	conn, err := net.Dial("unix", socketPath)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to socket %s: %w", socketPath, err)
	}
	defer conn.Close()

	// Set timeout
	deadline := time.Now().Add(10 * time.Second)
	if err := conn.SetDeadline(deadline); err != nil {
		return nil, fmt.Errorf("failed to set deadline: %w", err)
	}

	// Send request
	if _, err := conn.Write(requestJSON); err != nil {
		return nil, fmt.Errorf("failed to send request: %w", err)
	}

	// Read response
	var buf bytes.Buffer
	if _, err := buf.ReadFrom(conn); err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	// Parse response
	var response map[string]interface{}
	if err := json.Unmarshal(buf.Bytes(), &response); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	// Check for error
	if errObj, ok := response["error"]; ok {
		return nil, fmt.Errorf("RPC error: %v", errObj)
	}

	// Return result
	return response["result"], nil
}

// executeHTTPRPC executes an RPC call over HTTP
func executeHTTPRPC(endpoint string, requestJSON []byte) (interface{}, error) {
	// Create HTTP request
	req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(requestJSON))
	if err != nil {
		return nil, fmt.Errorf("failed to create HTTP request: %w", err)
	}

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

	// Create HTTP client with timeout
	client := &http.Client{
		Timeout: 10 * time.Second,
	}

	// Send request
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send HTTP request: %w", err)
	}
	defer resp.Body.Close()

	// Check status code
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("HTTP error: %s", resp.Status)
	}

	// Parse response
	var response map[string]interface{}
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	// Check for error
	if errObj, ok := response["error"]; ok {
		return nil, fmt.Errorf("RPC error: %v", errObj)
	}

	// Return result
	return response["result"], nil
}
