import Config from '#config';

class CircuitBreakerOpenError extends Error {
	constructor() {
		super('Circuit breaker is open');
		this.name = 'CircuitBreakerOpenError';
	}
}

class PercentageCircuitBreaker {
	private failures = 0;
	private successes = 0;
	private windowStart = Date.now();
	private windowDuration: number; // milliseconds
	private openUntil: number | null = null;
	private errorRateThreshold: number;
	private minRequests: number;
	private state: 'closed' | 'open' | 'half_open' = 'closed';
	private halfOpenSuccessCount = 0;
	private readonly HALF_OPEN_MAX_REQUESTS = 3;

	constructor(errorRateThreshold = 0.3, minRequests = 100, windowDuration = 60 * 1000) {
		this.errorRateThreshold = errorRateThreshold;
		this.minRequests = minRequests;
		this.windowDuration = windowDuration;
	}

	async execute<T>(fn: () => Promise<T>): Promise<T> {
		// Check if circuit is open or half-open
		if (this.state === 'open') {
			// Check if we should transition to half-open
			if (this.openUntil && Date.now() >= this.openUntil) {
				this.transitionToHalfOpen();
			} else {
				throw new CircuitBreakerOpenError();
			}
		}

		try {
			const result = await fn();
			this.recordSuccess();
			return result;
		} catch (error) {
			this.recordFailure();

			// Check if we should open the circuit
			if (this.shouldOpenCircuit()) {
				this.open();
				throw new CircuitBreakerOpenError();
			}

			throw error;
		}
	}

	getErrorRate(): number {
		const total = this.failures + this.successes;
		return total > 0 ? this.failures / total : 0;
	}

	getTotalRequests(): number {
		return this.failures + this.successes;
	}

	isOpen(): boolean {
		return this.state === 'open';
	}

	private shouldOpenCircuit(): boolean {
		return this.getErrorRate() > this.errorRateThreshold && this.getTotalRequests() >= this.minRequests;
	}

	private open(): void {
		this.state = 'open';
		this.openUntil = Date.now() + this.windowDuration;
		this.resetWindow();
	}

	private transitionToHalfOpen(): void {
		this.state = 'half_open';
		this.halfOpenSuccessCount = 0;
		this.resetWindow();
	}

	private resetWindow(): void {
		this.failures = 0;
		this.successes = 0;
		this.windowStart = Date.now();
	}

	private recordSuccess(): void {
		this.resetWindowIfNeeded();
		this.successes++;

		// If in half-open state, track successful requests
		if (this.state === 'half_open') {
			this.halfOpenSuccessCount++;
			// If enough successful requests in half-open, close the circuit
			if (this.halfOpenSuccessCount >= this.HALF_OPEN_MAX_REQUESTS) {
				this.state = 'closed';
				this.halfOpenSuccessCount = 0;
			}
		}
	}

	private recordFailure(): void {
		this.resetWindowIfNeeded();
		this.failures++;

		// If in half-open state and failure occurs, reopen the circuit immediately
		if (this.state === 'half_open') {
			this.open();
		}
	}

	private resetWindowIfNeeded(): void {
		if (Date.now() - this.windowStart > this.windowDuration) {
			this.resetWindow();
		}
	}

	getState(): 'open' | 'closed' | 'half_open' {
		return this.state;
	}

	getMetrics() {
		return {
			state: this.getState(),
			failures: this.failures,
			successes: this.successes,
			errorRate: this.getErrorRate(),
			totalRequests: this.getTotalRequests(),
			openUntil: this.openUntil,
		};
	}
}

// Circuit breaker registry for different providers
class CircuitBreakerRegistry {
	private static instance: CircuitBreakerRegistry;
	private breakers: Map<string, PercentageCircuitBreaker> = new Map();

	static getInstance(): CircuitBreakerRegistry {
		CircuitBreakerRegistry.instance ??= new CircuitBreakerRegistry();
		return CircuitBreakerRegistry.instance;
	}

	getBreaker(providerName: string): PercentageCircuitBreaker {
		if (!this.breakers.has(providerName)) {
			const errorRateThreshold = Config.circuitBreaker.errorRate;
			const minRequests = Config.circuitBreaker.minRequests;
			const timeout = Config.circuitBreaker.timeout;

			this.breakers.set(providerName, new PercentageCircuitBreaker(errorRateThreshold, minRequests, timeout));
		}
		return this.breakers.get(providerName)!;
	}

	getAllMetrics() {
		const metrics: Record<string, any> = {};
		for (const [name, breaker] of this.breakers.entries()) {
			metrics[name] = breaker.getMetrics();
		}
		return metrics;
	}
}

export { CircuitBreakerOpenError, PercentageCircuitBreaker, CircuitBreakerRegistry };
export default CircuitBreakerRegistry;
