package coredb

import (
	"database/sql"
	"fmt"
	"sync"
	"time"
)

// DBPoolConfig is the effective sql.DB pool configuration currently applied to the process.
type DBPoolConfig struct {
	MaxOpenConns    int
	MaxIdleConns    int
	ConnMaxLifetime time.Duration
	ConnMaxIdleTime time.Duration
}

// DBPoolStats is a stable API-friendly view of database/sql pool stats.
type DBPoolStats struct {
	MaxOpenConnections int
	OpenConnections    int
	InUse              int
	Idle               int
	WaitCount          int64
	WaitDuration       time.Duration
	MaxIdleClosed      int64
	MaxIdleTimeClosed  int64
	MaxLifetimeClosed  int64
}

// DBPoolSnapshot combines the effective pool config with current runtime stats.
type DBPoolSnapshot struct {
	Config DBPoolConfig
	Stats  DBPoolStats
}

// SQLDBPoolController owns runtime sql.DB pool reconfiguration and snapshots.
type SQLDBPoolController struct {
	db sqlDBPool

	mu     sync.RWMutex
	config DBPoolConfig
}

type sqlDBPool interface {
	SetMaxOpenConns(int)
	SetMaxIdleConns(int)
	SetConnMaxLifetime(time.Duration)
	SetConnMaxIdleTime(time.Duration)
	Stats() sql.DBStats
}

func NewSQLDBPoolController(db *sql.DB, initial DBPoolConfig) (*SQLDBPoolController, error) {
	if db == nil {
		return nil, fmt.Errorf("sql db is nil")
	}
	controller := &SQLDBPoolController{db: db}
	if _, err := controller.Update(initial); err != nil {
		return nil, err
	}
	return controller, nil
}

func ValidateDBPoolConfig(cfg DBPoolConfig) error {
	if cfg.MaxOpenConns < 0 {
		return fmt.Errorf("maxOpenConns must be non-negative")
	}
	if cfg.MaxIdleConns < 0 {
		return fmt.Errorf("maxIdleConns must be non-negative")
	}
	if cfg.MaxOpenConns > 0 && cfg.MaxIdleConns > cfg.MaxOpenConns {
		return fmt.Errorf("maxIdleConns must be less than or equal to maxOpenConns when maxOpenConns is set")
	}
	if cfg.ConnMaxLifetime < 0 {
		return fmt.Errorf("connMaxLifetime must be non-negative")
	}
	if cfg.ConnMaxIdleTime < 0 {
		return fmt.Errorf("connMaxIdleTime must be non-negative")
	}
	return nil
}

func (controller *SQLDBPoolController) Snapshot() DBPoolSnapshot {
	controller.mu.RLock()
	defer controller.mu.RUnlock()

	return controller.snapshotLocked()
}

func (controller *SQLDBPoolController) snapshotLocked() DBPoolSnapshot {
	stats := controller.db.Stats()
	return DBPoolSnapshot{
		Config: controller.config,
		Stats: DBPoolStats{
			MaxOpenConnections: stats.MaxOpenConnections,
			OpenConnections:    stats.OpenConnections,
			InUse:              stats.InUse,
			Idle:               stats.Idle,
			WaitCount:          stats.WaitCount,
			WaitDuration:       stats.WaitDuration,
			MaxIdleClosed:      stats.MaxIdleClosed,
			MaxIdleTimeClosed:  stats.MaxIdleTimeClosed,
			MaxLifetimeClosed:  stats.MaxLifetimeClosed,
		},
	}
}

func (controller *SQLDBPoolController) Update(cfg DBPoolConfig) (DBPoolSnapshot, error) {
	if controller == nil || controller.db == nil {
		return DBPoolSnapshot{}, fmt.Errorf("sql db pool controller is nil")
	}

	controller.mu.Lock()
	defer controller.mu.Unlock()

	if err := ValidateDBPoolConfig(cfg); err != nil {
		return DBPoolSnapshot{}, err
	}

	// Apply connection count constraints first so later setters observe the target ceiling.
	controller.db.SetMaxOpenConns(cfg.MaxOpenConns)
	controller.db.SetMaxIdleConns(cfg.MaxIdleConns)
	controller.db.SetConnMaxLifetime(cfg.ConnMaxLifetime)
	controller.db.SetConnMaxIdleTime(cfg.ConnMaxIdleTime)

	controller.config = cfg
	return controller.snapshotLocked(), nil
}
