package client

import (
	"errors"
	"sync"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/backoff"
	"google.golang.org/grpc/credentials/insecure"
)

type ConnectionPool interface {
	GetConnection() (*grpc.ClientConn, error)
	ReleaseConnection(*grpc.ClientConn)
	Close()
}

type connectionPoolImpl struct {
	address string
	maxSize int
	timeout time.Duration
	param   grpc.ConnectParams

	mu   sync.Mutex
	pool []*grpc.ClientConn
}

func NewConnectionPool(address string, maxSize int, timeout time.Duration) ConnectionPool {
	return &connectionPoolImpl{
		address: address,
		maxSize: maxSize,
		timeout: timeout,
		param: grpc.ConnectParams{
			Backoff: backoff.Config{
				BaseDelay:  1.0 * time.Second,
				Multiplier: 1.5,
				Jitter:     0.2,
				MaxDelay:   30 * time.Second,
			},
			MinConnectTimeout: 30 * time.Second,
		},
		pool: make([]*grpc.ClientConn, 0, maxSize),
	}
}

func (p *connectionPoolImpl) GetConnection() (*grpc.ClientConn, error) {
	p.mu.Lock()
	defer p.mu.Unlock()

	if p.pool == nil {
		return nil, errors.New("connection pool is closed")
	}

	// Check if there's any available connection in the pool
	if len(p.pool) > 0 {
		conn := p.pool[0]
		p.pool = p.pool[1:]
		return conn, nil
	}

	// If the pool is empty, create a new connection
	conn, err := grpc.Dial(p.address, grpc.WithBlock(),
		grpc.WithConnectParams(p.param),
		grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		return nil, err
	}
	return conn, nil
}

func (p *connectionPoolImpl) ReleaseConnection(conn *grpc.ClientConn) {
	p.mu.Lock()
	defer p.mu.Unlock()

	if p.pool != nil {
		// If the pool is full, close the connection
		if len(p.pool) >= p.maxSize {
			conn.Close()
			return
		}

		// Add the connection back to the pool
		p.pool = append(p.pool, conn)
	} else {
		conn.Close()
	}
}

func (p *connectionPoolImpl) Close() {
	p.mu.Lock()
	defer p.mu.Unlock()

	if p.pool != nil {
		for _, conn := range p.pool {
			conn.Close()
		}

		p.pool = nil
	}
}