package tcp

import (
	"agent/commons/crypto"
	"agent/commons/utime"
	"context"
	"errors"
	"io"
	"net"
	"sync"
	"time"
)

var (
	ErrClosed  = errors.New("pool is closed")
	ErrTimeout = errors.New("pool acquire timeout")
)

// tcpConn — объект, который лежит в пуле.
type tcpConn struct {
	index int64

	raw net.Conn

	reader       io.Reader
	streamCipher crypto.ICipher

	created int64 // UnixNano: когда создано (только один раз, в Factory)
	lastUse int64 // UnixNano: когда последний раз использовалось (выдача/возврат)

	closed bool
}

// Close закрывает соединение. Повторный вызов безопасен.
func (c *tcpConn) Close() error {
	if c == nil || c.closed {
		return nil
	}
	c.closed = true
	if c.raw != nil {
		return c.raw.Close()
	}
	return nil
}

// Factory создаёт новое соединение (включая инициализацию tcpConn.created/lastUse).
type Factory func(ctx context.Context) (*tcpConn, error)

// Validator проверяет пригодность соединения к повторному использованию.
type Validator func(c *tcpConn) bool

// Reset сбрасывает состояние перед возвратом в пул.
type Reset func(c *tcpConn)

type Config struct {
	// Указывает сколько соединений хранить в пуле. Не ограничивает общее количество создаваемых соединений.
	Size int

	Factory  Factory
	Validate Validator
	Reset    Reset

	MaxLifetime    utime.Duration
	MaxIdle        utime.Duration
	AcquireTimeout utime.Duration
}

type Pool struct {
	ch chan *tcpConn

	factory  Factory
	validate Validator
	reset    Reset

	maxLifetime    utime.Duration
	maxIdle        utime.Duration
	acquireTimeout utime.Duration

	mu     sync.Mutex
	closed bool
}

func NewPool(cfg Config) (*Pool, error) {
	if cfg.Size <= 0 {
		return nil, errors.New("Size must be > 0")
	}
	if cfg.Factory == nil {
		return nil, errors.New("Factory is required")
	}

	if cfg.Validate == nil {
		cfg.Validate = func(c *tcpConn) bool {
			// По умолчанию минимальная проверка
			return c != nil && c.raw != nil && !c.closed
		}
	}

	if cfg.Reset == nil {
		cfg.Reset = func(c *tcpConn) {
			// Сброс дедлайнов
			_ = c.raw.SetDeadline(utime.Time{})
		}
	}

	return &Pool{
		ch:             make(chan *tcpConn, cfg.Size),
		factory:        cfg.Factory,
		validate:       cfg.Validate,
		reset:          cfg.Reset,
		maxLifetime:    cfg.MaxLifetime,
		maxIdle:        cfg.MaxIdle,
		acquireTimeout: cfg.AcquireTimeout,
	}, nil
}

func (p *Pool) Size() int {
	return cap(p.ch)
}

func (p *Pool) MaxIdle() utime.Duration {
	p.mu.Lock()
	defer p.mu.Unlock()
	return p.maxIdle
}

func (p *Pool) SetMaxIdle(d utime.Duration) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.maxIdle = d
}

func (p *Pool) MaxLifetime() utime.Duration {
	p.mu.Lock()
	defer p.mu.Unlock()
	return p.maxLifetime
}

func (p *Pool) SetMaxLifetime(d utime.Duration) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.maxLifetime = d
}

func (p *Pool) AcquireTimeout() utime.Duration {
	p.mu.Lock()
	defer p.mu.Unlock()
	return p.acquireTimeout
}

func (p *Pool) SetAcquireTimeout(d utime.Duration) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.acquireTimeout = d
}

func (p *Pool) Close() error {
	p.mu.Lock()
	if p.closed {
		p.mu.Unlock()
		return nil
	}
	p.closed = true
	close(p.ch)
	p.mu.Unlock()

	var firstErr error
	for c := range p.ch {
		if err := c.Close(); err != nil && firstErr == nil {
			firstErr = err
		}
	}
	return firstErr
}

func (p *Pool) Acquire(ctx context.Context, timeout ...utime.Duration) (*tcpConn, error) {
	if p.isClosed() {
		return nil, ErrClosed
	}

	acquireTimeout := p.acquireTimeout
	if len(timeout) > 0 {
		acquireTimeout = timeout[0]
	}

	var timer <-chan utime.Time
	if acquireTimeout > 0 {
		t := time.NewTimer(acquireTimeout)
		defer t.Stop()
		timer = t.C
	}

	for {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case <-timer:
			return nil, ErrTimeout

		case c, ok := <-p.ch:
			if !ok || p.isClosed() {
				return nil, ErrClosed
			}

			// Ленивая очистка протухших/битых
			if p.expired(c) || !p.validate(c) {
				_ = c.Close()
				continue
			}

			c.lastUse = utime.NanoNow()
			return c, nil

		default:
			// Пул пуст — создаём новое соединение
			c, err := p.factory(ctx)
			if err != nil {
				return nil, err
			}

			// если Factory не выставила метаданные
			now := utime.NanoNow()
			if c.created == 0 {
				c.created = now
			}
			if c.lastUse == 0 {
				c.lastUse = now
			}

			return c, nil
		}
	}
}

// Release возвращает соединение в пул.
// bad=true — если во время использования произошла ошибка, после которой соединение нельзя reuse.
func (p *Pool) Release(c *tcpConn, bad bool) error {
	if c == nil {
		return nil
	}

	// Нельзя доверять — закрываем.
	if bad || p.isClosed() || p.expired(c) || !p.validate(c) {
		return c.Close()
	}

	if p.reset != nil {
		p.reset(c)
	}

	c.lastUse = utime.NanoNow()

	select {
	case p.ch <- c:
		return nil
	default:
		return c.Close()
	}
}

func (p *Pool) expired(c *tcpConn) bool {
	if c == nil || c.closed || c.raw == nil {
		return true
	}

	now := utime.NanoNow()

	if p.maxLifetime > 0 && c.created > 0 {
		if utime.Duration(now-c.created) > p.maxLifetime {
			return true
		}
	}

	if p.maxIdle > 0 && c.lastUse > 0 {
		if utime.Duration(now-c.lastUse) > p.maxIdle {
			return true
		}
	}

	return false
}

func (p *Pool) isClosed() bool {
	p.mu.Lock()
	defer p.mu.Unlock()
	return p.closed
}
