// Code generated by django-cfg centrifugo codegen. DO NOT EDIT.

package {{ package_name }}

import (
	"context"
	"crypto/rand"
	"encoding/json"
	"errors"
	"fmt"
	"sync"
	"time"

	"nhooyr.io/websocket"
	"nhooyr.io/websocket/wsjson"
)

// generateUUID creates a random UUID v4 using crypto/rand (stdlib only)
func generateUUID() string {
	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		// Fallback to timestamp if crypto fails
		return fmt.Sprintf("%d", time.Now().UnixNano())
	}

	// Set version (4) and variant (RFC4122)
	b[6] = (b[6] & 0x0f) | 0x40
	b[8] = (b[8] & 0x3f) | 0x80

	return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}

// CentrifugoRPCClient handles WebSocket connection and RPC call correlation.
type CentrifugoRPCClient struct {
	url          string
	token        string
	userID       string
	timeout      time.Duration
	conn         *websocket.Conn
	mu           sync.RWMutex
	pending      map[string]chan RPCResponse
	replyChannel string
	ctx          context.Context
	cancel       context.CancelFunc
	closeChan    chan struct{}
}

// RPCRequest represents an RPC request message.
type RPCRequest struct {
	Method        string      `json:"method"`
	Params        interface{} `json:"params"`
	CorrelationID string      `json:"correlation_id"`
	ReplyTo       string      `json:"reply_to"`
}

// RPCResponse represents an RPC response message.
type RPCResponse struct {
	CorrelationID string          `json:"correlation_id"`
	Result        json.RawMessage `json:"result,omitempty"`
	Error         *RPCError       `json:"error,omitempty"`
}

// RPCError represents an RPC error.
type RPCError struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

func (e *RPCError) Error() string {
	return fmt.Sprintf("RPC error %d: %s", e.Code, e.Message)
}

// NewCentrifugoRPCClient creates a new Centrifugo RPC client.
//
// Args:
//   - url: WebSocket URL (e.g., "ws://localhost:8000/connection/websocket")
//   - token: JWT authentication token
//   - userID: User ID for reply channel
//   - timeout: Default timeout for RPC calls
func NewCentrifugoRPCClient(url, token, userID string, timeout time.Duration) *CentrifugoRPCClient {
	ctx, cancel := context.WithCancel(context.Background())
	return &CentrifugoRPCClient{
		url:          url,
		token:        token,
		userID:       userID,
		timeout:      timeout,
		pending:      make(map[string]chan RPCResponse),
		replyChannel: fmt.Sprintf("user#%s", userID),
		ctx:          ctx,
		cancel:       cancel,
		closeChan:    make(chan struct{}),
	}
}

// Connect establishes WebSocket connection to Centrifugo.
func (c *CentrifugoRPCClient) Connect(ctx context.Context) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.conn != nil {
		return errors.New("already connected")
	}

	// Connect to WebSocket
	conn, _, err := websocket.Dial(ctx, c.url, &websocket.DialOptions{
		Subprotocols: []string{"centrifuge-protobuf"},
	})
	if err != nil {
		return fmt.Errorf("failed to dial: %w", err)
	}

	c.conn = conn

	// Start message reader goroutine
	go c.readLoop()

	// Subscribe to reply channel
	if err := c.subscribe(ctx, c.replyChannel); err != nil {
		c.conn.Close(websocket.StatusNormalClosure, "")
		c.conn = nil
		return fmt.Errorf("failed to subscribe: %w", err)
	}

	return nil
}

// subscribe subscribes to a Centrifugo channel
func (c *CentrifugoRPCClient) subscribe(ctx context.Context, channel string) error {
	subscribeMsg := map[string]interface{}{
		"subscribe": map[string]interface{}{
			"channel": channel,
		},
	}

	return wsjson.Write(ctx, c.conn, subscribeMsg)
}

// readLoop reads messages from WebSocket connection
func (c *CentrifugoRPCClient) readLoop() {
	defer func() {
		c.mu.Lock()
		close(c.closeChan)
		c.mu.Unlock()
	}()

	for {
		select {
		case <-c.ctx.Done():
			return
		default:
		}

		var msg map[string]interface{}
		err := wsjson.Read(c.ctx, c.conn, &msg)
		if err != nil {
			if websocket.CloseStatus(err) != websocket.StatusNormalClosure {
				fmt.Printf("WebSocket read error: %v\n", err)
			}
			return
		}

		// Handle publication messages (responses to our RPC calls)
		if pub, ok := msg["pub"].(map[string]interface{}); ok {
			if data, ok := pub["data"].(map[string]interface{}); ok {
				c.handleResponse(data)
			}
		}
	}
}

// handleResponse processes incoming RPC responses
func (c *CentrifugoRPCClient) handleResponse(data map[string]interface{}) {
	correlationID, ok := data["correlation_id"].(string)
	if !ok {
		return
	}

	c.mu.RLock()
	responseChan, exists := c.pending[correlationID]
	c.mu.RUnlock()

	if !exists {
		return
	}

	// Parse response
	var response RPCResponse
	response.CorrelationID = correlationID

	if result, ok := data["result"]; ok {
		resultBytes, _ := json.Marshal(result)
		response.Result = resultBytes
	}

	if errData, ok := data["error"].(map[string]interface{}); ok {
		rpcErr := &RPCError{}
		if code, ok := errData["code"].(float64); ok {
			rpcErr.Code = int(code)
		}
		if msg, ok := errData["message"].(string); ok {
			rpcErr.Message = msg
		}
		response.Error = rpcErr
	}

	// Send response to waiting goroutine
	select {
	case responseChan <- response:
	case <-time.After(time.Second):
		fmt.Printf("Timeout sending response for correlation_id: %s\n", correlationID)
	}
}

// Call performs an RPC call with automatic serialization/deserialization.
//
// Args:
//   - ctx: Context for timeout and cancellation
//   - method: RPC method name (e.g., "tasks.get_stats")
//   - params: Request parameters (will be JSON-serialized)
//   - result: Pointer to result struct (will be JSON-deserialized)
//
// Returns error if call fails or times out.
func (c *CentrifugoRPCClient) Call(ctx context.Context, method string, params interface{}, result interface{}) error {
	correlationID := generateUUID()

	// Create request
	request := RPCRequest{
		Method:        method,
		Params:        params,
		CorrelationID: correlationID,
		ReplyTo:       c.replyChannel,
	}

	// Create response channel
	responseChan := make(chan RPCResponse, 1)
	c.mu.Lock()
	c.pending[correlationID] = responseChan
	c.mu.Unlock()

	defer func() {
		c.mu.Lock()
		delete(c.pending, correlationID)
		c.mu.Unlock()
		close(responseChan)
	}()

	// Publish request to rpc.requests channel
	publishMsg := map[string]interface{}{
		"publish": map[string]interface{}{
			"channel": "rpc.requests",
			"data":    request,
		},
	}

	if err := wsjson.Write(ctx, c.conn, publishMsg); err != nil {
		return fmt.Errorf("failed to publish request: %w", err)
	}

	// Wait for response with timeout
	timeout := c.timeout
	if deadline, ok := ctx.Deadline(); ok {
		timeout = time.Until(deadline)
	}

	select {
	case response := <-responseChan:
		if response.Error != nil {
			return response.Error
		}
		if result != nil && len(response.Result) > 0 {
			return json.Unmarshal(response.Result, result)
		}
		return nil

	case <-time.After(timeout):
		return fmt.Errorf("RPC call timed out after %v", timeout)

	case <-ctx.Done():
		return ctx.Err()

	case <-c.closeChan:
		return errors.New("connection closed")
	}
}

// Disconnect closes the WebSocket connection.
func (c *CentrifugoRPCClient) Disconnect() error {
	c.cancel()

	c.mu.Lock()
	defer c.mu.Unlock()

	if c.conn == nil {
		return nil
	}

	err := c.conn.Close(websocket.StatusNormalClosure, "client disconnect")
	c.conn = nil
	return err
}
