import sinon from "sinon";
import {
	createCheck,
	getChecksCount,
	getChecks,
	getTeamChecks,
	deleteChecks,
	deleteChecksByTeamId,
	updateChecksTTL,
} from "../../db/mongo/modules/checkModule.js";
import Check from "../../db/models/Check.js";
import Monitor from "../../db/models/Monitor.js";
import User from "../../db/models/User.js";
import logger from "../../utils/logger.js";

describe("checkModule", function () {
	describe("createCheck", function () {
		let checkCountDocumentsStub, checkSaveStub, monitorFindByIdStub, monitorSaveStub;
		const mockMonitor = {
			_id: "123",
			uptimePercentage: 0.5,
			status: true,
			save: () => this,
		};
		const mockCheck = { active: true };

		beforeEach(function () {
			checkSaveStub = sinon.stub(Check.prototype, "save");
			checkCountDocumentsStub = sinon.stub(Check, "countDocuments");
			monitorFindByIdStub = sinon.stub(Monitor, "findById");
			monitorSaveStub = sinon.stub(Monitor.prototype, "save");
		});

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

		it("should return undefined early if no monitor is found", async function () {
			monitorFindByIdStub.returns(null);
			const check = await createCheck({ monitorId: "123" });
			expect(check).to.be.undefined;
		});

		it("should return a check", async function () {
			monitorFindByIdStub.returns(mockMonitor);
			checkSaveStub.returns(mockCheck);
			monitorSaveStub.returns(mockMonitor);
			const check = await createCheck({ monitorId: "123", status: true });
			expect(check).to.deep.equal(mockCheck);
		});

		it("should return a check if status is down", async function () {
			mockMonitor.status = false;
			monitorFindByIdStub.returns(mockMonitor);
			checkSaveStub.returns(mockCheck);
			monitorSaveStub.returns(mockMonitor);
			const check = await createCheck({ monitorId: "123", status: false });
			expect(check).to.deep.equal(mockCheck);
		});

		it("should return a check if uptimePercentage is undefined", async function () {
			mockMonitor.uptimePercentage = undefined;
			monitorFindByIdStub.returns(mockMonitor);
			checkSaveStub.returns(mockCheck);
			monitorSaveStub.returns(mockMonitor);
			const check = await createCheck({ monitorId: "123", status: true });
			expect(check).to.deep.equal(mockCheck);
		});

		it("should return a check if uptimePercentage is undefined and status is down", async function () {
			mockMonitor.uptimePercentage = undefined;
			monitorFindByIdStub.returns(mockMonitor);
			checkSaveStub.returns(mockCheck);
			monitorSaveStub.returns(mockMonitor);
			const check = await createCheck({ monitorId: "123", status: false });
			expect(check).to.deep.equal(mockCheck);
		});

		it("should monitor save error", async function () {
			const err = new Error("Save Error");
			monitorSaveStub.throws(err);
			try {
				await createCheck({ monitorId: "123" });
			} catch (error) {
				expect(error).to.deep.equal(err);
			}
		});

		it("should handle errors", async function () {
			const err = new Error("DB Error");
			checkCountDocumentsStub.throws(err);
			try {
				await createCheck({ monitorId: "123" });
			} catch (error) {
				expect(error).to.deep.equal(err);
			}
		});
	});

	describe("getChecksCount", function () {
		let checkCountDocumentStub;

		beforeEach(function () {
			checkCountDocumentStub = sinon.stub(Check, "countDocuments");
		});

		afterEach(function () {
			checkCountDocumentStub.restore();
		});

		it("should return count with basic monitorId query", async function () {
			const req = {
				params: { monitorId: "test123" },
				query: {},
			};
			checkCountDocumentStub.resolves(5);

			const result = await getChecksCount(req);

			expect(result).to.equal(5);
			expect(checkCountDocumentStub.calledOnce).to.be.true;
			expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({
				monitorId: "test123",
			});
		});

		it("should include dateRange in query when provided", async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { dateRange: "day" },
			};
			checkCountDocumentStub.resolves(3);

			const result = await getChecksCount(req);

			expect(result).to.equal(3);
			expect(checkCountDocumentStub.firstCall.args[0]).to.have.property("createdAt");
			expect(checkCountDocumentStub.firstCall.args[0].createdAt).to.have.property("$gte");
		});

		it('should handle "all" filter correctly', async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { filter: "all" },
			};
			checkCountDocumentStub.resolves(2);

			const result = await getChecksCount(req);

			expect(result).to.equal(2);
			expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({
				monitorId: "test123",
				status: false,
			});
		});

		it('should handle "down" filter correctly', async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { filter: "down" },
			};
			checkCountDocumentStub.resolves(2);

			const result = await getChecksCount(req);

			expect(result).to.equal(2);
			expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({
				monitorId: "test123",
				status: false,
			});
		});

		it('should handle "resolve" filter correctly', async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { filter: "resolve" },
			};
			checkCountDocumentStub.resolves(1);

			const result = await getChecksCount(req);

			expect(result).to.equal(1);
			expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({
				monitorId: "test123",
				status: false,
				statusCode: 5000,
			});
		});

		it("should handle unknown filter correctly", async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { filter: "unknown" },
			};
			checkCountDocumentStub.resolves(1);

			const result = await getChecksCount(req);

			expect(result).to.equal(1);
			expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({
				monitorId: "test123",
				status: false,
			});
		});

		it("should combine dateRange and filter in query", async function () {
			const req = {
				params: { monitorId: "test123" },
				query: {
					dateRange: "week",
					filter: "down",
				},
			};
			checkCountDocumentStub.resolves(4);

			const result = await getChecksCount(req);

			expect(result).to.equal(4);
			expect(checkCountDocumentStub.firstCall.args[0]).to.have.all.keys(
				"monitorId",
				"createdAt",
				"status"
			);
		});
	});

	describe("getChecks", function () {
		let checkFindStub, monitorFindStub;

		beforeEach(function () {
			checkFindStub = sinon.stub(Check, "find").returns({
				skip: sinon.stub().returns({
					limit: sinon.stub().returns({
						sort: sinon.stub().returns([{ id: 1 }, { id: 2 }]),
					}),
				}),
			});
		});

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

		it("should return checks with basic monitorId query", async function () {
			const req = {
				params: { monitorId: "test123" },
				query: {},
			};

			const result = await getChecks(req);

			expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
		});

		it("should return checks with limit query", async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { limit: 10 },
			};

			const result = await getChecks(req);

			expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
		});

		it("should handle pagination correctly", async function () {
			const req = {
				params: { monitorId: "test123" },
				query: {
					page: 2,
					rowsPerPage: 10,
				},
			};

			const result = await getChecks(req);

			expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
		});

		it("should handle dateRange filter", async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { dateRange: "week" },
			};
			const result = await getChecks(req);

			expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
		});

		it('should handle "all" filter', async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { filter: "all" },
			};

			await getChecks(req);
			const result = await getChecks(req);
			expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
		});

		it('should handle "down" filter', async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { filter: "down" },
			};

			await getChecks(req);
			const result = await getChecks(req);
			expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
		});

		it('should handle "resolve" filter', async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { filter: "resolve" },
			};

			await getChecks(req);
			const result = await getChecks(req);
			expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
		});

		it('should handle "unknown" filter', async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { filter: "unknown" },
			};

			await getChecks(req);
			const result = await getChecks(req);
			expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
		});

		it("should handle ascending sort order", async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { sortOrder: "asc" },
			};

			await getChecks(req);
			const result = await getChecks(req);
			expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
		});

		it("should handle error case", async function () {
			const req = {
				params: { monitorId: "test123" },
				query: {},
			};

			checkFindStub.throws(new Error("Database error"));

			try {
				await getChecks(req);
			} catch (error) {
				expect(error.message).to.equal("Database error");
				expect(error.service).to.equal("checkModule");
				expect(error.method).to.equal("getChecks");
			}
		});
	});

	describe("getTeamChecks", function () {
		let checkFindStub, checkCountDocumentsStub, monitorFindStub;
		const mockMonitors = [{ _id: "123" }];

		beforeEach(function () {
			monitorFindStub = sinon.stub(Monitor, "find").returns({
				select: sinon.stub().returns(mockMonitors),
			});
			checkCountDocumentsStub = sinon.stub(Check, "countDocuments").returns(2);
			checkFindStub = sinon.stub(Check, "find").returns({
				skip: sinon.stub().returns({
					limit: sinon.stub().returns({
						sort: sinon.stub().returns({
							select: sinon.stub().returns([{ id: 1 }, { id: 2 }]),
						}),
					}),
				}),
			});
		});

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

		it("should return checks with basic monitorId query", async function () {
			const req = {
				params: { teamId: "test123" },
				query: {},
			};

			const result = await getTeamChecks(req);
			expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
		});

		it("should handle pagination correctly", async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { limit: 1, page: 2, rowsPerPage: 10 },
			};

			const result = await getTeamChecks(req);
			expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
		});

		it("should handle dateRange filter", async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { dateRange: "week" },
			};
			const result = await getTeamChecks(req);
			expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
		});

		it('should handle "all" filter', async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { filter: "all" },
			};

			await getChecks(req);
			const result = await getTeamChecks(req);
			expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
		});

		it('should handle "down" filter', async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { filter: "down" },
			};

			await getChecks(req);
			const result = await getTeamChecks(req);
			expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
		});

		it('should handle "resolve" filter', async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { filter: "resolve" },
			};

			await getChecks(req);
			const result = await getTeamChecks(req);
			expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
		});

		it('should handle "unknown" filter', async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { filter: "unknown" },
			};

			await getChecks(req);
			const result = await getTeamChecks(req);
			expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
		});

		it("should handle ascending sort order", async function () {
			const req = {
				params: { monitorId: "test123" },
				query: { sortOrder: "asc" },
			};

			await getChecks(req);
			const result = await getTeamChecks(req);
			expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
		});

		it("should handle error case", async function () {
			const req = {
				params: { monitorId: "test123" },
				query: {},
			};

			checkFindStub.throws(new Error("Database error"));

			try {
				await getTeamChecks(req);
			} catch (error) {
				expect(error.message).to.equal("Database error");
				expect(error.service).to.equal("checkModule");
				expect(error.method).to.equal("getTeamChecks");
			}
		});
	});

	describe("deleteChecks", function () {
		let checkDeleteManyStub;

		beforeEach(function () {
			checkDeleteManyStub = sinon.stub(Check, "deleteMany").resolves({ deletedCount: 1 });
		});

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

		it("should return a value if a check is deleted", async function () {
			const result = await deleteChecks("123");
			expect(result).to.equal(1);
		});

		it("should handle an error", async function () {
			checkDeleteManyStub.throws(new Error("Database error"));
			try {
				await deleteChecks("123");
			} catch (error) {
				expect(error.message).to.equal("Database error");
				expect(error.method).to.equal("deleteChecks");
			}
		});
	});

	describe("deleteChecksByTeamId", function () {
		let mockMonitors = [{ _id: 123, save: () => this }];
		let monitorFindStub, monitorSaveStub, checkDeleteManyStub;

		beforeEach(function () {
			monitorSaveStub = sinon.stub(Monitor.prototype, "save");
			monitorFindStub = sinon.stub(Monitor, "find").returns(mockMonitors);
			checkDeleteManyStub = sinon.stub(Check, "deleteMany").resolves({ deletedCount: 1 });
		});

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

		it("should return a deleted count", async function () {
			const result = await deleteChecksByTeamId("123");
			expect(result).to.equal(1);
		});

		it("should handle errors", async function () {
			const err = new Error("DB Error");
			monitorFindStub.throws(err);
			try {
				const result = await deleteChecksByTeamId("123");
			} catch (error) {
				expect(error).to.deep.equal(err);
			}
		});
	});

	describe("updateChecksTTL", function () {
		let userUpdateManyStub;
		let loggerStub;

		beforeEach(function () {
			loggerStub = sinon.stub(logger, "error");
			userUpdateManyStub = sinon.stub(User, "updateMany");
			Check.collection = { dropIndex: sinon.stub(), createIndex: sinon.stub() };
		});

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

		it("should return undefined", async function () {
			const result = await updateChecksTTL("123", 10);
			expect(result).to.be.undefined;
		});

		it("should log an error if dropIndex throws an error", async function () {
			const err = new Error("Drop Index Error");
			Check.collection.dropIndex.throws(err);
			await updateChecksTTL("123", 10);
			expect(loggerStub.calledOnce).to.be.true;
			expect(loggerStub.firstCall.args[0].message).to.equal(err.message);
		});

		it("should throw an error if createIndex throws an error", async function () {
			const err = new Error("Create Index Error");
			Check.collection.createIndex.throws(err);
			try {
				await updateChecksTTL("123", 10);
			} catch (error) {
				expect(error).to.deep.equal(err);
			}
		});

		it("should throw an error if User.updateMany throws an error", async function () {
			const err = new Error("Update Many Error");
			userUpdateManyStub.throws(err);
			try {
				await updateChecksTTL("123", 10);
			} catch (error) {
				expect(error).to.deep.equal(err);
			}
		});
	});
});
