import {adjustedInterval, Logger} from '@ztimson/utils';

const cacheOptions = {
	ttl: null,
	reload: null,
	retry: 3,
	url: null,
}

export class CacheService {
	#fetchLoop;

	cached = null;
	lastUpdate = null;
	logger;
	name;
	options;
	pending;

	constructor(name, options) {
		this.name = name;
		this.options = {
			...cacheOptions,
			ttl: options.reload,
			...options
		};
		this.logger = new Logger(name);
		if(this.options.reload) setTimeout(() => this.startLoop(), 1000);
	}

	async #fetchWithRetry() {
		let lastError;
		for(let attempt = 1; attempt <= this.options.retry; attempt++) {
			try {
				return await this.fetch();
			} catch(err) {
				lastError = err;
				if(attempt < this.options.retry)
					this.logger.warn(`Retrying (${attempt + 1}/${this.options.retry}): ${err.message || err.toString()}`);
			}
		}
		throw lastError;
	}

	async fetch() {
		if(!this.options.url) return Promise.reject('Fetch is not configured');
		const resp = await fetch(this.options.url);
		if(!resp.ok) throw new Error(`${resp.status} ${resp.statusText}`);
		return resp.json();
	}

	startLoop(speed = this.options.reload) {
		this.stopLoop();
		this.#fetchLoop = adjustedInterval(() => this.get(), speed);
	}

	stopLoop() {
		if(!this.#fetchLoop) return;
		clearInterval(this.#fetchLoop);
		this.#fetchLoop = null;
	}

	update(catchErr = true) {
		if(this.pending) return this.pending;
		this.logger.info('Fetching latest');
		this.pending = this.#fetchWithRetry().then(data => {
			if(data?.err) throw new Error(data.err?.stack || data.err?.message || data.err);
			if(data?.error) throw new Error(data.error?.stack || data.error?.message || data.error);
			this.lastUpdate = new Date();
			this.cached = data || null;
			this.logger.debug('Finished updating');
			return {timestamp: this.lastUpdate, data: this.cached};
		}).catch(err => {
			if(catchErr) this.logger.error(`Failed: ${typeof err == 'object' ? (err.stackTrace || err.message) : err}`)
			else throw err;
		}).finally(() => this.pending = null);
		return this.pending;
	}

	async get(wait = false) {
		if(!this.cached || !this.lastUpdate || this.lastUpdate?.getTime() + this.options.ttl <= Date.now()) {
			if(wait) await this.update();
			else this.update();
		}
		return Promise.resolve({timestamp: this.lastUpdate || null, data: this.cached || null});
	}
}

export class AuroraService extends CacheService {
	constructor() {
		super('Aurora', {
			reload: 60_000 * 15,
			url: 'https://services.swpc.noaa.gov/json/ovation_aurora_latest.json',
		});
	}
}

export const Aurora = new AuroraService();
