import sinon from "sinon";
import SettingsService from "../../service/settingsService.js";
import { expect } from "chai";
import NetworkService from "../../service/networkService.js";
const SERVICE_NAME = "SettingsService";

describe("SettingsService", function () {
	let sandbox, mockAppSettings;

	beforeEach(function () {
		sandbox = sinon.createSandbox();
		sandbox.stub(process.env, "CLIENT_HOST").value("http://localhost");
		sandbox.stub(process.env, "JWT_SECRET").value("secret");
		sandbox.stub(process.env, "REFRESH_TOKEN_SECRET").value("refreshSecret");
		sandbox.stub(process.env, "DB_TYPE").value("postgres");
		sandbox
			.stub(process.env, "DB_CONNECTION_STRING")
			.value("postgres://user:pass@localhost/db");
		sandbox.stub(process.env, "REDIS_HOST").value("localhost");
		sandbox.stub(process.env, "REDIS_PORT").value("6379");
		sandbox.stub(process.env, "TOKEN_TTL").value("3600");
		sandbox.stub(process.env, "REFRESH_TOKEN_TTL").value("86400");
		sandbox.stub(process.env, "PAGESPEED_API_KEY").value("apiKey");
		sandbox.stub(process.env, "SYSTEM_EMAIL_HOST").value("smtp.mailtrap.io");
		sandbox.stub(process.env, "SYSTEM_EMAIL_PORT").value("2525");
		sandbox.stub(process.env, "SYSTEM_EMAIL_ADDRESS").value("test@example.com");
		sandbox.stub(process.env, "SYSTEM_EMAIL_PASSWORD").value("password");
	});

	mockAppSettings = {
		settingOne: 123,
		settingTwo: 456,
	};

	afterEach(function () {
		sandbox.restore();
		sinon.restore();
	});

	describe("constructor", function () {
		it("should construct a new SettingsService", function () {
			const settingsService = new SettingsService(mockAppSettings);
			expect(settingsService.appSettings).to.equal(mockAppSettings);
		});
	});

	describe("loadSettings", function () {
		it("should load settings from DB when environment variables are not set", async function () {
			const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
			const appSettings = { findOne: sinon.stub().returns(dbSettings) };
			const settingsService = new SettingsService(appSettings);
			settingsService.settings = {};
			const result = await settingsService.loadSettings();
			expect(result).to.deep.equal(dbSettings);
		});

		it("should throw an error if settings are not found", async function () {
			const appSettings = { findOne: sinon.stub().returns(null) };
			const settingsService = new SettingsService(appSettings);
			settingsService.settings = null;

			try {
				await settingsService.loadSettings();
			} catch (error) {
				expect(error.message).to.equal("Settings not found");
				expect(error.service).to.equal(SERVICE_NAME);
				expect(error.method).to.equal("loadSettings");
			}
		});

		it("should add its method and service name to error if not present", async function () {
			const appSettings = { findOne: sinon.stub().throws(new Error("Test error")) };
			const settingsService = new SettingsService(appSettings);
			try {
				await settingsService.loadSettings();
			} catch (error) {
				expect(error.message).to.equal("Test error");
				expect(error.service).to.equal(SERVICE_NAME);
				expect(error.method).to.equal("loadSettings");
			}
		});

		it("should not add its method and service name to error if present", async function () {
			const error = new Error("Test error");
			error.method = "otherMethod";
			error.service = "OTHER_SERVICE";
			const appSettings = { findOne: sinon.stub().throws(error) };
			const settingsService = new SettingsService(appSettings);
			try {
				await settingsService.loadSettings();
			} catch (error) {
				expect(error.message).to.equal("Test error");
				expect(error.service).to.equal("OTHER_SERVICE");
				expect(error.method).to.equal("otherMethod");
			}
		});

		it("should merge DB settings with environment variables", async function () {
			const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
			const appSettings = { findOne: sinon.stub().returns(dbSettings) };
			const settingsService = new SettingsService(appSettings);
			const result = await settingsService.loadSettings();
			expect(result).to.deep.equal(settingsService.settings);
			expect(settingsService.settings.logLevel).to.equal("debug");
			expect(settingsService.settings.apiBaseUrl).to.equal("http://localhost");
		});
	});

	describe("reloadSettings", function () {
		it("should call loadSettings", async function () {
			const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
			const appSettings = { findOne: sinon.stub().returns(dbSettings) };
			const settingsService = new SettingsService(appSettings);
			settingsService.settings = {};
			const result = await settingsService.reloadSettings();
			expect(result).to.deep.equal(dbSettings);
		});
	});

	describe("getSettings", function () {
		it("should return the current settings", function () {
			const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
			const appSettings = { findOne: sinon.stub().returns(dbSettings) };
			const settingsService = new SettingsService(appSettings);
			settingsService.settings = dbSettings;
			const result = settingsService.getSettings();
			expect(result).to.deep.equal(dbSettings);
		});

		it("should throw an error if settings have not been loaded", function () {
			const appSettings = { findOne: sinon.stub().returns(null) };
			const settingsService = new SettingsService(appSettings);
			settingsService.settings = null;

			try {
				settingsService.getSettings();
			} catch (error) {
				expect(error.message).to.equal("Settings have not been loaded");
			}
		});
	});
});
