package smtp

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

var ErrCircuitOpen = errors.New("smtp circuit open")

// CircuitBreaker tracks consecutive SMTP failures per mail account.
type CircuitBreaker struct {
	threshold   int
	cooldown    time.Duration
	mu          sync.Mutex
	accounts    map[string]*circuitState
}

type circuitState struct {
	failures    int
	openUntil   time.Time
	halfOpenTry bool
}

func NewCircuitBreaker(threshold int, cooldown time.Duration) *CircuitBreaker {
	if threshold < 1 {
		threshold = 5
	}
	if cooldown <= 0 {
		cooldown = 5 * time.Minute
	}
	return &CircuitBreaker{
		threshold: threshold,
		cooldown:  cooldown,
		accounts:  make(map[string]*circuitState),
	}
}

func (cb *CircuitBreaker) Allow(accountID string) error {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	st := cb.state(accountID)
	now := time.Now()
	if st.openUntil.IsZero() || now.After(st.openUntil) {
		if !st.openUntil.IsZero() {
			st.halfOpenTry = true
		}
		return nil
	}
	return ErrCircuitOpen
}

func (cb *CircuitBreaker) RecordSuccess(accountID string) {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	st := cb.state(accountID)
	st.failures = 0
	st.openUntil = time.Time{}
	st.halfOpenTry = false
}

func (cb *CircuitBreaker) RecordFailure(accountID string) {
	cb.mu.Lock()
	defer cb.mu.Unlock()

	st := cb.state(accountID)
	if st.halfOpenTry {
		st.halfOpenTry = false
		st.failures = cb.threshold
		st.openUntil = time.Now().Add(cb.cooldown)
		return
	}
	st.failures++
	if st.failures >= cb.threshold {
		st.openUntil = time.Now().Add(cb.cooldown)
	}
}

func (cb *CircuitBreaker) state(accountID string) *circuitState {
	st, ok := cb.accounts[accountID]
	if !ok {
		st = &circuitState{}
		cb.accounts[accountID] = st
	}
	return st
}
