import { currencyModel, type Currency } from "./entities";
import { ERROR_CODES, type Result } from "@pkg/result";
import { CURRENCIES } from "$lib/domains/currency/data/currencies";
import type Redis from "ioredis";
import { betterFetch } from "@better-fetch/fetch";
import { getError, Logger } from "@pkg/logger";

export class CurrencyRepository {
	private store: Redis;
	private key = "currency:exchange:rates";

	constructor(redis: Redis) {
		this.store = redis;
	}

	private async getRates(): Promise<Result<Record<string, number>>> {
		try {
			const found = await this.store.get(this.key);
			if (!found) {
				return {};
			}
			return { data: JSON.parse(found) };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.API_ERROR,
						message: "Failed to fetch currency exchange rates",
						detail: "Failed to retrieve currency exchange rates from cache",
						userHint: "Try again later?",
						actionable: false,
					},
					err,
				),
			};
		}
	}

	async getCurrencies(depth = 1): Promise<Result<Currency[]>> {
		let ratesRes = await this.getRates();
		if (ratesRes.error) {
			return { error: ratesRes.error };
		}
		if (!ratesRes.data) {
			await this.refreshCurrenciesRate();
			if (depth > 3) {
				return {
					error: getError({
						code: ERROR_CODES.INTERNAL_SERVER_ERROR,
						message: "Could not get currency info at this moment",
						detail: "Max retries reached in trying to refresh currency exchange rates",
						userHint: "Try again later?",
						actionable: false,
					}),
				};
			}
			return this.getCurrencies(depth++);
		}

		const rates = ratesRes.data;
		const out = new Array<Currency>();

		let i = 0;
		for (const cCode of Object.keys(rates)) {
			i++;
			const found = CURRENCIES.find((c) => c.code === cCode);
			const exchangeRate = rates[cCode];
			const ratio = Math.round((1 / exchangeRate) * 10 ** 6) / 10 ** 6;

			const parsed = currencyModel.safeParse({
				id: i,
				code: cCode.toUpperCase(),
				currency:
					found && found.currency
						? `${cCode} (${found?.currency})`
						: "Unknown",
				exchangeRate,
				ratio,
			});
			if (parsed.error) {
				continue;
			}
			out.push(parsed.data);
		}

		return { data: out };
	}

	async refreshCurrenciesRate(): Promise<Result<boolean>> {
		const response = await betterFetch<{
			result: string;
			provider: string;
			documentation: string;
			terms_of_use: string;
			time_last_update_unix: number;
			time_last_update_utc: string;
			time_next_update_unix: number;
			time_next_update_utc: string;
			time_eol_unix: number;
			base_code: string;
			rates: Record<string, number>;
		}>("https://open.er-api.com/v6/latest/USD");

		if (response.error) {
			return {
				error: getError({
					code: ERROR_CODES.API_ERROR,
					message: "Failed to fetch currency exchange rates",
					detail:
						response.error.message ??
						"Unknown error while fetching currency exchange rates",
					userHint: "Try again later?",
					actionable: false,
				}),
			};
		}

		const ONE_DAY_IN_SECONDS = 86400;

		if (response.data) {
			Logger.info(`Updating currency exchange rates to the new one`);

			await this.store.del(this.key);
			await this.store.setex(
				this.key,
				// For 24 hours, it'll be enough
				ONE_DAY_IN_SECONDS,
				JSON.stringify(response.data.rates),
			);
		}

		return { data: false };
	}
}
