package push

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

// Circuit breaker states
const (
	stateClosed   = iota // Normal operation, requests pass through
	stateOpen            // Too many failures, requests are rejected
	stateHalfOpen        // Testing recovery, one request allowed through
)

// Default circuit breaker settings
const (
	defaultFailureThreshold = 5                // Open after this many consecutive failures
	defaultRecoveryTimeout  = 30 * time.Second // Try again after this duration
)

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

// CircuitBreaker implements a simple circuit breaker pattern for external service calls.
// It is thread-safe and requires no external dependencies.
//
// States:
//   - Closed: normal operation, all requests pass through. Consecutive failures are counted.
//   - Open: after reaching the failure threshold, all requests are immediately rejected
//     with ErrCircuitOpen until the recovery timeout elapses.
//   - Half-Open: after the recovery timeout, one request is allowed through. If it
//     succeeds the breaker resets to Closed; if it fails it returns to Open.
type CircuitBreaker struct {
	mu               sync.Mutex
	state            int
	failureCount     int
	failureThreshold int
	recoveryTimeout  time.Duration
	lastFailureTime  time.Time
	name             string // For logging
}

// CircuitBreakerOption configures a CircuitBreaker.
type CircuitBreakerOption func(*CircuitBreaker)

// WithFailureThreshold sets the number of consecutive failures before opening the circuit.
func WithFailureThreshold(n int) CircuitBreakerOption {
	return func(cb *CircuitBreaker) {
		if n > 0 {
			cb.failureThreshold = n
		}
	}
}

// WithRecoveryTimeout sets how long the circuit stays open before trying half-open.
func WithRecoveryTimeout(d time.Duration) CircuitBreakerOption {
	return func(cb *CircuitBreaker) {
		if d > 0 {
			cb.recoveryTimeout = d
		}
	}
}

// NewCircuitBreaker creates a new CircuitBreaker with the given name and options.
// The name is used for logging and identification.
func NewCircuitBreaker(name string, opts ...CircuitBreakerOption) *CircuitBreaker {
	cb := &CircuitBreaker{
		state:            stateClosed,
		failureThreshold: defaultFailureThreshold,
		recoveryTimeout:  defaultRecoveryTimeout,
		name:             name,
	}
	for _, opt := range opts {
		opt(cb)
	}
	return cb
}

// Allow checks whether a request should be allowed through.
// It returns true if the request can proceed, false if the circuit is open.
// When transitioning from open to half-open, it returns true for the probe request.
func (cb *CircuitBreaker) Allow() bool {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	switch cb.state {
	case stateClosed:
		return true
	case stateOpen:
		// Check if recovery timeout has elapsed
		if time.Since(cb.lastFailureTime) >= cb.recoveryTimeout {
			cb.state = stateHalfOpen
			return true
		}
		return false
	case stateHalfOpen:
		// Only one request at a time in half-open state.
		// The first caller that got here via Allow() is already in flight;
		// reject subsequent callers until that probe resolves.
		return false
	default:
		return true
	}
}

// RecordSuccess records a successful request. If the breaker is half-open, it resets to closed.
func (cb *CircuitBreaker) RecordSuccess() {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	cb.failureCount = 0
	cb.state = stateClosed
}

// RecordFailure records a failed request. If the failure threshold is reached, the
// breaker transitions to the open state.
func (cb *CircuitBreaker) RecordFailure() {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	cb.failureCount++
	cb.lastFailureTime = time.Now()

	if cb.failureCount >= cb.failureThreshold {
		cb.state = stateOpen
	}
}

// State returns the current state of the circuit breaker as a human-readable string.
func (cb *CircuitBreaker) State() string {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	switch cb.state {
	case stateClosed:
		return "closed"
	case stateOpen:
		return "open"
	case stateHalfOpen:
		return "half-open"
	default:
		return "unknown"
	}
}

// Name returns the circuit breaker's name.
func (cb *CircuitBreaker) Name() string {
	return cb.name
}

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

	cb.state = stateClosed
	cb.failureCount = 0
	cb.lastFailureTime = time.Time{}
}

// Counts returns the current failure count (useful for testing and monitoring).
func (cb *CircuitBreaker) Counts() int {
	cb.mu.Lock()
	defer cb.mu.Unlock()
	return cb.failureCount
}
