package protocol

import (
	"context"
	"fmt"
	"net"
	"sync"
	"time"
)

// Client permet d'appeler un microservice via Unix socket.
type Client struct {
	socketPath string
	conn       net.Conn
	mu         sync.Mutex
}

// NewClient crée un nouveau client.
func NewClient(socketPath string) *Client {
	return &Client{
		socketPath: socketPath,
	}
}

// Connect établit la connexion au socket.
func (c *Client) Connect() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.conn != nil {
		return nil // Déjà connecté
	}

	conn, err := net.Dial("unix", c.socketPath)
	if err != nil {
		return fmt.Errorf("dial %s: %w", c.socketPath, err)
	}
	c.conn = conn
	return nil
}

// Close ferme la connexion.
func (c *Client) Close() error {
	c.mu.Lock()
	defer c.mu.Unlock()

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

// Call envoie une requête et attend la réponse.
func (c *Client) Call(ctx context.Context, req *Request) (*Response, error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.conn == nil {
		return nil, fmt.Errorf("not connected")
	}

	// Timeout
	timeout := time.Duration(req.TimeoutMs) * time.Millisecond
	if timeout == 0 {
		timeout = 5 * time.Second
	}
	deadline := time.Now().Add(timeout)
	c.conn.SetDeadline(deadline)

	// Encoder et envoyer la requête
	data, err := Encode(req)
	if err != nil {
		return nil, fmt.Errorf("encode request: %w", err)
	}

	if err := writeMessage(c.conn, data); err != nil {
		c.conn = nil // Connexion cassée
		return nil, fmt.Errorf("write request: %w", err)
	}

	// Lire la réponse
	respData, err := readMessage(c.conn)
	if err != nil {
		c.conn = nil // Connexion cassée
		return nil, fmt.Errorf("read response: %w", err)
	}

	resp, err := DecodeResponse(respData)
	if err != nil {
		return nil, fmt.Errorf("decode response: %w", err)
	}

	return resp, nil
}

// CallAction raccourci pour appeler une action.
func (c *Client) CallAction(ctx context.Context, action string, params map[string]any) (*Response, error) {
	req := NewRequest(action, params)
	return c.Call(ctx, req)
}

// Pool gère un pool de connexions vers un service.
type Pool struct {
	socketPath string
	clients    chan *Client
	maxSize    int
}

// NewPool crée un pool de connexions.
func NewPool(socketPath string, size int) *Pool {
	return &Pool{
		socketPath: socketPath,
		clients:    make(chan *Client, size),
		maxSize:    size,
	}
}

// Get obtient un client du pool.
func (p *Pool) Get() (*Client, error) {
	select {
	case client := <-p.clients:
		return client, nil
	default:
		// Créer un nouveau client
		client := NewClient(p.socketPath)
		if err := client.Connect(); err != nil {
			return nil, err
		}
		return client, nil
	}
}

// Put remet un client dans le pool.
func (p *Pool) Put(client *Client) {
	select {
	case p.clients <- client:
		// OK, remis dans le pool
	default:
		// Pool plein, fermer le client
		client.Close()
	}
}

// Call obtient un client, exécute l'appel, et remet le client.
func (p *Pool) Call(ctx context.Context, req *Request) (*Response, error) {
	client, err := p.Get()
	if err != nil {
		return nil, err
	}

	resp, err := client.Call(ctx, req)
	if err != nil {
		client.Close()
		return nil, err
	}

	p.Put(client)
	return resp, nil
}

// Close ferme tous les clients du pool.
func (p *Pool) Close() {
	close(p.clients)
	for client := range p.clients {
		client.Close()
	}
}
