// Code generated by django-ipc - DO NOT EDIT.
// Generated at: {{ generated_at }}

package {{ package_name }}

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/gorilla/websocket"

	"rpcclient/pkg/types"
)

// Environment represents server environment
type Environment string

const (
{% if environments %}
{% for env, config in environments.items() %}
	// Env{{ env|capitalize }} - {{ env }} environment
	Env{{ env|capitalize }} Environment = "{{ env }}"
{% endfor %}
{% else %}
	// EnvDevelopment - development environment
	EnvDevelopment Environment = "development"
{% endif %}
)

// Config holds environment-specific configuration
type Config struct {
	WebSocketURL string
	RedisURL     string
}

// GetConfig returns configuration for given environment
func GetConfig(env Environment) Config {
{% if environments %}
	configs := map[Environment]Config{
{% for env, config in environments.items() %}
		Env{{ env|capitalize }}: {
			WebSocketURL: "{{ config.websocket_url }}",
			RedisURL:     "{{ config.redis_url }}",
		},
{% endfor %}
	}

	if cfg, ok := configs[env]; ok {
		return cfg
	}
{% endif %}

	// Default fallback
	return Config{
		WebSocketURL: "ws://localhost:8765",
		RedisURL:     "redis://localhost:6379/2",
	}
}

// RPCRequest represents an outgoing RPC request
type RPCRequest struct {
	Type   string      `json:"type"`
	Method string      `json:"method"`
	Params interface{} `json:"params"`
	ID     string      `json:"id"`
}

// RPCResponse represents an incoming RPC response
type RPCResponse struct {
	Type   string          `json:"type"`
	Result json.RawMessage `json:"result,omitempty"`
	Error  *RPCError       `json:"error,omitempty"`
	ID     string          `json:"id"`
}

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

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

// PendingRequest tracks pending RPC calls
type PendingRequest struct {
	Response chan *RPCResponse
	Timeout  *time.Timer
}

// Client is the WebSocket RPC client
type Client struct {
	url    string
	token  string
	conn   *websocket.Conn
	mu     sync.RWMutex

	// RPC state
	pending map[string]*PendingRequest

	// Connection state
	connected bool
	closed    bool

	// Configuration
	timeout         time.Duration
	enableLogging   bool

	// Event handlers
	eventHandlers map[string]EventHandler

	// Close signal
	closeChan chan struct{}
}

// EventHandler handles incoming server-side events
type EventHandler func(method string, params json.RawMessage) error

// IncomingMessage represents server-to-client messages
type IncomingMessage struct {
	Type   string          `json:"type"`
	Method string          `json:"method,omitempty"`
	Params json.RawMessage `json:"params,omitempty"`
	Result json.RawMessage `json:"result,omitempty"`
	Error  *RPCError       `json:"error,omitempty"`
	ID     string          `json:"id,omitempty"`
}

// ClientOption configures the client
type ClientOption func(*Client)

// WithToken sets the JWT authentication token
func WithToken(token string) ClientOption {
	return func(c *Client) {
		c.token = token
	}
}

// WithTimeout sets the RPC call timeout
func WithTimeout(timeout time.Duration) ClientOption {
	return func(c *Client) {
		c.timeout = timeout
	}
}

// WithLogging enables debug logging
func WithLogging(enabled bool) ClientOption {
	return func(c *Client) {
		c.enableLogging = enabled
	}
}

// NewClient creates a new WebSocket RPC client
func NewClient(url string, opts ...ClientOption) *Client {
	c := &Client{
		url:           url,
		pending:       make(map[string]*PendingRequest),
		eventHandlers: make(map[string]EventHandler),
		timeout:       30 * time.Second,
		enableLogging: false,
		closeChan:     make(chan struct{}),
	}

	for _, opt := range opts {
		opt(c)
	}

	return c
}

// NewClientWithEnvironment creates client for specific environment
func NewClientWithEnvironment(env Environment, opts ...ClientOption) *Client {
	config := GetConfig(env)
	return NewClient(config.WebSocketURL, opts...)
}

// Connect establishes WebSocket connection
func (c *Client) Connect() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.connected {
		return nil
	}

	if c.closed {
		return errors.New("client is closed")
	}

	// Establish WebSocket connection
	conn, _, err := websocket.DefaultDialer.Dial(c.url, nil)
	if err != nil {
		return fmt.Errorf("failed to connect: %w", err)
	}

	c.conn = conn

	// Send authentication message if token is provided
	if c.token != "" {
		authMsg := "Bearer " + c.token
		if err := conn.WriteMessage(websocket.TextMessage, []byte(authMsg)); err != nil {
			conn.Close()
			return fmt.Errorf("failed to authenticate: %w", err)
		}

		// Read auth response
		_, msg, err := conn.ReadMessage()
		if err != nil {
			conn.Close()
			return fmt.Errorf("failed to read auth response: %w", err)
		}

		var authResp map[string]interface{}
		if err := json.Unmarshal(msg, &authResp); err != nil {
			conn.Close()
			return fmt.Errorf("failed to parse auth response: %w", err)
		}

		if authResp["status"] != "ok" {
			conn.Close()
			return errors.New("authentication failed")
		}

		if c.enableLogging {
			log.Printf("[RPC] Authenticated successfully")
		}
	}

	c.connected = true

	// Start message handler
	go c.handleMessages()

	if c.enableLogging {
		log.Printf("[RPC] Connected to %s", c.url)
	}

	return nil
}

// Close closes the WebSocket connection
func (c *Client) Close() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.closed {
		return nil
	}

	c.closed = true
	close(c.closeChan)

	// Cancel all pending requests
	for id, pending := range c.pending {
		pending.Timeout.Stop()
		close(pending.Response)
		delete(c.pending, id)
	}

	if c.conn != nil {
		err := c.conn.Close()
		c.conn = nil
		c.connected = false
		return err
	}

	return nil
}

// IsConnected returns whether the client is connected
func (c *Client) IsConnected() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.connected
}

// OnEvent registers an event handler for specific RPC method
func (c *Client) OnEvent(method string, handler EventHandler) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.eventHandlers[method] = handler
}

// handleMessages processes incoming WebSocket messages
func (c *Client) handleMessages() {
	defer func() {
		c.mu.Lock()
		c.connected = false
		c.mu.Unlock()
	}()

	for {
		select {
		case <-c.closeChan:
			return
		default:
		}

		_, msg, err := c.conn.ReadMessage()
		if err != nil {
			if c.enableLogging {
				log.Printf("[RPC] Error reading message: %v", err)
			}
			return
		}

		// Parse as generic incoming message first
		var incoming IncomingMessage
		if err := json.Unmarshal(msg, &incoming); err != nil {
			if c.enableLogging {
				log.Printf("[RPC] Error parsing message: %v", err)
			}
			continue
		}

		// Check if this is a response to our request
		if incoming.ID != "" {
			c.mu.Lock()
			pending, ok := c.pending[incoming.ID]
			if ok {
				delete(c.pending, incoming.ID)
				pending.Timeout.Stop()
			}
			c.mu.Unlock()

			if ok {
				// Convert to RPCResponse format
				resp := &RPCResponse{
					Type:   incoming.Type,
					Result: incoming.Result,
					Error:  incoming.Error,
					ID:     incoming.ID,
				}
				pending.Response <- resp
				close(pending.Response)
				continue
			}
		}

		// Check if this is an incoming RPC call from server
		if incoming.Type == "rpc" && incoming.Method != "" {
			if c.enableLogging {
				log.Printf("[RPC] ← Incoming event: %s", incoming.Method)
			}

			// Look for registered handler
			c.mu.RLock()
			handler, ok := c.eventHandlers[incoming.Method]
			c.mu.RUnlock()

			if ok {
				// Call handler in goroutine
				go func() {
					if err := handler(incoming.Method, incoming.Params); err != nil {
						if c.enableLogging {
							log.Printf("[RPC] Event handler error: %v", err)
						}
					}
				}()
			} else if c.enableLogging {
				log.Printf("[RPC] No handler registered for: %s", incoming.Method)
			}
		}
	}
}

// call performs a synchronous RPC call
func (c *Client) call(method string, params interface{}) (json.RawMessage, error) {
	c.mu.RLock()
	if !c.connected {
		c.mu.RUnlock()
		return nil, errors.New("not connected")
	}
	c.mu.RUnlock()

	// Generate request ID
	requestID := uuid.New().String()

	// Create request
	req := RPCRequest{
		Type:   "rpc",
		Method: method,
		Params: params,
		ID:     requestID,
	}

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

	// Create pending request
	responseChan := make(chan *RPCResponse, 1)
	timeout := time.AfterFunc(c.timeout, func() {
		c.mu.Lock()
		defer c.mu.Unlock()

		if pending, ok := c.pending[requestID]; ok {
			delete(c.pending, requestID)
			pending.Response <- &RPCResponse{
				Error: &RPCError{
					Code:    -32000,
					Message: "request timeout",
				},
			}
			close(pending.Response)
		}
	})

	c.mu.Lock()
	c.pending[requestID] = &PendingRequest{
		Response: responseChan,
		Timeout:  timeout,
	}
	c.mu.Unlock()

	// Send request
	if err := c.conn.WriteMessage(websocket.TextMessage, data); err != nil {
		c.mu.Lock()
		delete(c.pending, requestID)
		c.mu.Unlock()
		timeout.Stop()
		return nil, fmt.Errorf("failed to send request: %w", err)
	}

	if c.enableLogging {
		log.Printf("[RPC] → %s (id: %s)", method, requestID)
	}

	// Wait for response
	resp := <-responseChan

	if c.enableLogging {
		if resp.Error != nil {
			log.Printf("[RPC] ← %s (id: %s) ERROR: %v", method, requestID, resp.Error)
		} else {
			log.Printf("[RPC] ← %s (id: %s) OK", method, requestID)
		}
	}

	if resp.Error != nil {
		return nil, resp.Error
	}

	return resp.Result, nil
}

{% for method in methods %}
// {{ method.go_name }} - {{ method.description }}
func (c *Client) {{ method.go_name }}({% if method.params_type == "map[string]interface{}" %}params map[string]interface{}{% else %}params *types.{{ method.params_type }}{% endif %}) ({% if method.result_type == "map[string]interface{}" %}*map[string]interface{}{% else %}*types.{{ method.result_type }}{% endif %}, error) {
	result, err := c.call("{{ method.original_name }}", params)
	if err != nil {
		return nil, err
	}

{% if method.result_type == "map[string]interface{}" %}
	var response map[string]interface{}
{% else %}
	var response types.{{ method.result_type }}
{% endif %}
	if err := json.Unmarshal(result, &response); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return &response, nil
}

{% endfor %}
