package tenantmanager

import (
	"context"
	"database/sql"
	"errors"
	"testing"

	"go.uber.org/zap"
)

type stubPoolManager struct {
	getPool    func(user, password string) (poolConnProvider, error)
	closeCount int
}

func (s *stubPoolManager) GetPool(user, password string) (poolConnProvider, error) {
	if s.getPool != nil {
		return s.getPool(user, password)
	}
	return nil, errors.New("GetPool not configured")
}

func (s *stubPoolManager) Close() error {
	s.closeCount++
	return nil
}

type stubPool struct {
	connErr error
}

func (s *stubPool) Conn(ctx context.Context) (*sql.Conn, error) {
	return nil, s.connErr
}

func TestTenantProxyLimiter(t *testing.T) {
	tenant := &TenantContext{
		proxyLimiter: newLimiter(1),
	}

	if !tenant.AcquireProxySlot() {
		t.Fatalf("expected first proxy slot acquisition to succeed")
	}
	if tenant.AcquireProxySlot() {
		t.Fatalf("expected proxy slot acquisition to fail when limit reached")
	}
	tenant.ReleaseProxySlot()
	if !tenant.AcquireProxySlot() {
		t.Fatalf("expected proxy slot acquisition after release to succeed")
	}
}

func TestTenantMySQLLimiterExceeded(t *testing.T) {
	tenant := &TenantContext{
		poolManager:  &stubPoolManager{},
		mysqlLimiter: newLimiter(1),
	}

	if !tenant.mysqlLimiter.Acquire() {
		t.Fatalf("expected limiter acquisition to succeed")
	}
	conn, release, err := tenant.LeaseMySQLConn(context.Background(), "user", "password")
	if !errors.Is(err, ErrMySQLLimitExceeded) {
		t.Fatalf("expected ErrMySQLLimitExceeded, got %v", err)
	}
	if conn != nil || release != nil {
		t.Fatalf("expected no lease when limit exceeded")
	}
}

func TestLeaseMySQLConnReleasesLimiterOnPoolError(t *testing.T) {
	pm := &stubPoolManager{
		getPool: func(user, password string) (poolConnProvider, error) {
			return nil, errors.New("pool error")
		},
	}
	tenant := &TenantContext{
		poolManager:  pm,
		mysqlLimiter: newLimiter(1),
	}

	if _, _, err := tenant.LeaseMySQLConn(context.Background(), "user", "password"); err == nil {
		t.Fatalf("expected pool error")
	}
	if tenant.mysqlLimiter.Count() != 0 {
		t.Fatalf("expected limiter to be released after pool error")
	}
	if _, _, err := tenant.LeaseMySQLConn(context.Background(), "user", "password"); err == nil || errors.Is(err, ErrMySQLLimitExceeded) {
		t.Fatalf("expected pool error without limiter exhaustion")
	}
}

func TestLeaseMySQLConnReleasesLimiterOnConnError(t *testing.T) {
	pm := &stubPoolManager{
		getPool: func(user, password string) (poolConnProvider, error) {
			return &stubPool{connErr: errors.New("conn error")}, nil
		},
	}
	tenant := &TenantContext{
		poolManager:  pm,
		mysqlLimiter: newLimiter(1),
	}

	if _, _, err := tenant.LeaseMySQLConn(context.Background(), "user", "password"); err == nil {
		t.Fatalf("expected conn error")
	}
	if tenant.mysqlLimiter.Count() != 0 {
		t.Fatalf("expected limiter to be released after conn error")
	}
}

func TestPruneIdleTenants(t *testing.T) {
	idlePool := &stubPoolManager{}
	busyPool := &stubPoolManager{}
	idle := &TenantContext{
		tenantID:     "idle",
		poolManager:  idlePool,
		proxyLimiter: newLimiter(1),
		mysqlLimiter: newLimiter(1),
	}
	busy := &TenantContext{
		tenantID:     "busy",
		poolManager:  busyPool,
		proxyLimiter: newLimiter(1),
		mysqlLimiter: newLimiter(1),
	}
	if !busy.AcquireProxySlot() {
		t.Fatalf("expected proxy slot acquisition to succeed")
	}

	manager := &TenantManager{
		tenants: map[string]*TenantContext{
			"idle": idle,
			"busy": busy,
		},
		logger: zap.NewNop(),
	}

	removed := manager.PruneIdleTenants()
	if removed != 1 {
		t.Fatalf("removed = %d, want 1", removed)
	}
	if idlePool.closeCount != 1 {
		t.Fatalf("expected idle pool to close once, got %d", idlePool.closeCount)
	}
	if busyPool.closeCount != 0 {
		t.Fatalf("expected busy pool to remain open, got %d", busyPool.closeCount)
	}

	manager.mu.Lock()
	_, idleOK := manager.tenants["idle"]
	_, busyOK := manager.tenants["busy"]
	manager.mu.Unlock()
	if idleOK {
		t.Fatalf("expected idle tenant to be pruned")
	}
	if !busyOK {
		t.Fatalf("expected busy tenant to remain")
	}
}
