// Package circuitbreaker provides protection against cascading failures.
//
// The circuit breaker pattern prevents repeated calls to a failing service,
// allowing it time to recover. After a threshold of failures, the circuit
// "opens" and returns errors immediately without attempting the operation.
package circuitbreaker

import (
	"errors"
	"sync"
	"time"
)

// State represents the circuit breaker state.
type State int

const (
	// Closed is the normal operating state - requests are allowed through.
	Closed State = iota
	// Open means the circuit is tripped - requests fail immediately.
	Open
	// HalfOpen means we're testing if the service has recovered.
	HalfOpen
)

func (s State) String() string {
	switch s {
	case Closed:
		return "closed"
	case Open:
		return "open"
	case HalfOpen:
		return "half-open"
	default:
		return "unknown"
	}
}

// Errors returned by the circuit breaker.
var (
	ErrCircuitOpen = errors.New("circuit breaker is open")
)

// StateChangeCallback is called when the circuit breaker state changes.
type StateChangeCallback func(name string, from, to State)

// Config configures the circuit breaker behavior.
type Config struct {
	// Name is the identifier for this circuit breaker (used in callbacks).
	Name string

	// FailureThreshold is the number of consecutive failures before opening.
	// Default: 5
	FailureThreshold int

	// ResetTimeout is how long to wait before attempting recovery (half-open).
	// Default: 30 seconds
	ResetTimeout time.Duration

	// HalfOpenRequests is how many requests to allow in half-open state.
	// Default: 1
	HalfOpenRequests int

	// OnStateChange is called when the circuit breaker state changes.
	OnStateChange StateChangeCallback
}

// DefaultConfig returns sensible defaults.
func DefaultConfig() Config {
	return Config{
		FailureThreshold: 5,
		ResetTimeout:     30 * time.Second,
		HalfOpenRequests: 1,
	}
}

// CircuitBreaker implements the circuit breaker pattern.
type CircuitBreaker struct {
	cfg Config

	mu               sync.RWMutex
	state            State
	failures         int
	successes        int
	lastFailure      time.Time
	halfOpenRequests int
}

// New creates a new circuit breaker with the given configuration.
func New(cfg Config) *CircuitBreaker {
	if cfg.FailureThreshold <= 0 {
		cfg.FailureThreshold = 5
	}
	if cfg.ResetTimeout <= 0 {
		cfg.ResetTimeout = 30 * time.Second
	}
	if cfg.HalfOpenRequests <= 0 {
		cfg.HalfOpenRequests = 1
	}

	return &CircuitBreaker{
		cfg:   cfg,
		state: Closed,
	}
}

// Execute runs the function if the circuit allows it.
// Returns ErrCircuitOpen if the circuit is open.
func (cb *CircuitBreaker) Execute(fn func() error) error {
	if !cb.canExecute() {
		return ErrCircuitOpen
	}

	err := fn()
	cb.recordResult(err)
	return err
}

// canExecute checks if a request should be allowed.
func (cb *CircuitBreaker) canExecute() bool {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	switch cb.state {
	case Closed:
		return true

	case Open:
		// Check if reset timeout has passed
		if time.Since(cb.lastFailure) > cb.cfg.ResetTimeout {
			oldState := cb.state
			cb.state = HalfOpen
			cb.halfOpenRequests = 0
			if cb.cfg.OnStateChange != nil {
				cb.cfg.OnStateChange(cb.cfg.Name, oldState, cb.state)
			}
			return true
		}
		return false

	case HalfOpen:
		// Allow limited requests in half-open state
		if cb.halfOpenRequests < cb.cfg.HalfOpenRequests {
			cb.halfOpenRequests++
			return true
		}
		return false
	}

	return false
}

// recordResult updates state based on operation outcome.
func (cb *CircuitBreaker) recordResult(err error) {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	if err != nil {
		cb.onFailure()
	} else {
		cb.onSuccess()
	}
}

// onFailure handles a failed operation.
func (cb *CircuitBreaker) onFailure() {
	cb.failures++
	cb.successes = 0
	cb.lastFailure = time.Now()

	oldState := cb.state
	switch cb.state {
	case Closed:
		if cb.failures >= cb.cfg.FailureThreshold {
			cb.state = Open
		}
	case HalfOpen:
		cb.state = Open
	}

	if oldState != cb.state && cb.cfg.OnStateChange != nil {
		cb.cfg.OnStateChange(cb.cfg.Name, oldState, cb.state)
	}
}

// onSuccess handles a successful operation.
func (cb *CircuitBreaker) onSuccess() {
	cb.successes++

	oldState := cb.state
	switch cb.state {
	case Closed:
		cb.failures = 0
	case HalfOpen:
		// Successful probe - close the circuit
		cb.state = Closed
		cb.failures = 0
	}

	if oldState != cb.state && cb.cfg.OnStateChange != nil {
		cb.cfg.OnStateChange(cb.cfg.Name, oldState, cb.state)
	}
}

// State returns the current circuit state.
func (cb *CircuitBreaker) State() State {
	cb.mu.RLock()
	defer cb.mu.RUnlock()
	return cb.state
}

// Stats returns current circuit statistics.
func (cb *CircuitBreaker) Stats() Stats {
	cb.mu.RLock()
	defer cb.mu.RUnlock()

	return Stats{
		State:       cb.state,
		Failures:    cb.failures,
		Successes:   cb.successes,
		LastFailure: cb.lastFailure,
	}
}

// Reset manually resets the circuit breaker to closed state.
func (cb *CircuitBreaker) Reset() {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	cb.state = Closed
	cb.failures = 0
	cb.successes = 0
	cb.lastFailure = time.Time{}
	cb.halfOpenRequests = 0
}

// Stats contains circuit breaker statistics.
type Stats struct {
	State       State
	Failures    int
	Successes   int
	LastFailure time.Time
}
