// Package httpclient provides circuit breaker protection for HTTP clients.
package httpclient

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

// ErrCircuitOpen is returned when the circuit breaker is open and requests are blocked.
var ErrCircuitOpen = errors.New("circuit breaker is open")

// CircuitState represents the current state of a circuit breaker.
type CircuitState int

const (
	// CircuitClosed allows all requests through and monitors for failures.
	CircuitClosed CircuitState = iota
	// CircuitOpen blocks all requests and waits for the reset timeout.
	CircuitOpen
	// CircuitHalfOpen allows a single test request to determine if the circuit should close.
	CircuitHalfOpen
)

func (s CircuitState) String() string {
	switch s {
	case CircuitClosed:
		return "closed"
	case CircuitOpen:
		return "open"
	case CircuitHalfOpen:
		return "half-open"
	default:
		return "unknown"
	}
}

// CircuitBreakerConfig configures the circuit breaker behavior.
type CircuitBreakerConfig struct {
	// FailureThreshold is the number of consecutive failures before opening the circuit.
	// Default: 5
	FailureThreshold int

	// ResetTimeout is how long to wait before attempting to close the circuit.
	// Default: 30s
	ResetTimeout time.Duration

	// HalfOpenMaxRequests is the number of test requests to allow in half-open state.
	// Default: 1
	HalfOpenMaxRequests int
}

// DefaultCircuitBreakerConfig returns sensible defaults for circuit breaker configuration.
func DefaultCircuitBreakerConfig() CircuitBreakerConfig {
	return CircuitBreakerConfig{
		FailureThreshold:    5,
		ResetTimeout:        30 * time.Second,
		HalfOpenMaxRequests: 1,
	}
}

// CircuitBreaker implements the circuit breaker pattern for protecting services
// from cascading failures. It tracks consecutive failures and opens the circuit
// when a threshold is reached, preventing further requests until a reset timeout.
type CircuitBreaker struct {
	config CircuitBreakerConfig

	mu                 sync.Mutex
	state              CircuitState
	consecutiveFailures int
	lastFailure        time.Time
	halfOpenRequests   int
}

// NewCircuitBreaker creates a new circuit breaker with the given configuration.
// If config fields are zero, defaults are applied.
func NewCircuitBreaker(config CircuitBreakerConfig) *CircuitBreaker {
	if config.FailureThreshold == 0 {
		config.FailureThreshold = 5
	}
	if config.ResetTimeout == 0 {
		config.ResetTimeout = 30 * time.Second
	}
	if config.HalfOpenMaxRequests == 0 {
		config.HalfOpenMaxRequests = 1
	}

	return &CircuitBreaker{
		config: config,
		state:  CircuitClosed,
	}
}

// Allow checks if a request should be allowed through the circuit breaker.
// Returns nil if the request is allowed, ErrCircuitOpen if blocked.
func (cb *CircuitBreaker) Allow() error {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	switch cb.state {
	case CircuitClosed:
		return nil

	case CircuitOpen:
		// Check if reset timeout has elapsed
		if time.Since(cb.lastFailure) >= cb.config.ResetTimeout {
			cb.state = CircuitHalfOpen
			cb.halfOpenRequests = 0
			return nil
		}
		return ErrCircuitOpen

	case CircuitHalfOpen:
		// Allow limited requests in half-open state
		if cb.halfOpenRequests < cb.config.HalfOpenMaxRequests {
			cb.halfOpenRequests++
			return nil
		}
		return ErrCircuitOpen
	}

	return nil
}

// RecordSuccess records a successful request and potentially closes an open circuit.
func (cb *CircuitBreaker) RecordSuccess() {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	switch cb.state {
	case CircuitHalfOpen:
		// Test request succeeded, close the circuit
		cb.state = CircuitClosed
		cb.consecutiveFailures = 0
		cb.halfOpenRequests = 0

	case CircuitClosed:
		// Reset failure count on success
		cb.consecutiveFailures = 0
	}
}

// RecordFailure records a failed request and potentially opens the circuit.
func (cb *CircuitBreaker) RecordFailure() {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	cb.consecutiveFailures++
	cb.lastFailure = time.Now()

	switch cb.state {
	case CircuitClosed:
		// Check if we've hit the failure threshold
		if cb.consecutiveFailures >= cb.config.FailureThreshold {
			cb.state = CircuitOpen
		}

	case CircuitHalfOpen:
		// Test request failed, re-open the circuit
		cb.state = CircuitOpen
		cb.halfOpenRequests = 0
	}
}

// State returns the current state of the circuit breaker.
func (cb *CircuitBreaker) State() CircuitState {
	cb.mu.Lock()
	defer cb.mu.Unlock()
	return cb.state
}

// ConsecutiveFailures returns the current consecutive failure count.
func (cb *CircuitBreaker) ConsecutiveFailures() int {
	cb.mu.Lock()
	defer cb.mu.Unlock()
	return cb.consecutiveFailures
}
