package snell

import (
	"context"
	"net"
	"time"

	"github.com/Dreamacro/clash/component/pool"
	"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
)

type Pool struct {
	pool *pool.Pool[*Snell]
}

func (p *Pool) Get() (net.Conn, error) {
	return p.GetContext(context.Background())
}

func (p *Pool) GetContext(ctx context.Context) (net.Conn, error) {
	elm, err := p.pool.GetContext(ctx)
	if err != nil {
		return nil, err
	}

	return &PoolConn{elm, p}, nil
}

func (p *Pool) Put(conn *Snell) {
	if err := HalfClose(conn); err != nil {
		_ = conn.Close()
		return
	}

	p.pool.Put(conn)
}

type PoolConn struct {
	*Snell
	pool *Pool
}

func (pc *PoolConn) Read(b []byte) (int, error) {
	// save old status of reply (it mutable by Read)
	reply := pc.Snell.reply

	n, err := pc.Snell.Read(b)
	if err == shadowaead.ErrZeroChunk {
		// if reply is false, it should be client halfclose.
		// ignore error and read data again.
		if !reply {
			pc.Snell.reply = false
			return pc.Snell.Read(b)
		}
	}
	return n, err
}

func (pc *PoolConn) Write(b []byte) (int, error) {
	return pc.Snell.Write(b)
}

func (pc *PoolConn) Close() error {
	// clash use SetReadDeadline to break bidirectional copy between client and server.
	// reset it before reuse connection to avoid io timeout error.
	_ = pc.Snell.Conn.SetReadDeadline(time.Time{})
	pc.pool.Put(pc.Snell)
	return nil
}

func NewPool(factory func(context.Context) (*Snell, error)) *Pool {
	p := pool.New[*Snell](
		func(ctx context.Context) (*Snell, error) {
			return factory(ctx)
		},
		pool.WithAge[*Snell](15000),
		pool.WithSize[*Snell](10),
		pool.WithEvict[*Snell](func(item *Snell) {
			_ = item.Close()
		}),
	)

	return &Pool{pool: p}
}
