import test from "node:test";
import assert from "node:assert/strict";
import request from "supertest";
import { setup, signup } from "./helpers.js";

async function bootstrapPlan(agent, csrf) {
	const r = await agent
		.post("/api/plans")
		.set("x-csrf-token", csrf)
		.send({ plan: { fields: { 0.1: "Test coffee" } } });
	return r.body.plan.id;
}

test("inventory: auth and CSRF are required on every route", async () => {
	const { app } = await setup();
	const anon = request.agent(app);
	assert.equal((await anon.get("/api/inventory")).status, 401);
	assert.equal((await anon.post("/api/inventory").send({})).status, 401);
	const { agent, csrf } = await (async () => {
		const a = request.agent(app);
		const { csrf: c } = await signup(a, "owner@example.com");
		return { agent: a, csrf: c };
	})();
	assert.equal(
		(await agent.post("/api/inventory").send({ origin: "X", initialWeightG: 100 })).status,
		403,
	);
	const created = await agent
		.post("/api/inventory")
		.set("x-csrf-token", csrf)
		.send({ origin: "Huila", initialWeightG: 1000 });
	assert.equal(created.status, 201);
	assert.equal(
		(await agent.put(`/api/inventory/${created.body.lot.id}`).send({ origin: "Y" })).status,
		403,
	);
	assert.equal(
		(await agent.delete(`/api/inventory/${created.body.lot.id}`)).status,
		403,
	);
	assert.equal(
		(
			await agent
				.post(`/api/inventory/${created.body.lot.id}/consume`)
				.send({ weightG: 10 })
		).status,
		403,
	);
});

test("inventory: create, list, and ownership isolation", async () => {
	const { app } = await setup();
	const first = request.agent(app);
	const second = request.agent(app);
	const { csrf: firstCsrf } = await signup(first, "one@example.com");
	await signup(second, "two@example.com");

	const bad = await first
		.post("/api/inventory")
		.set("x-csrf-token", firstCsrf)
		.send({ origin: "", initialWeightG: 100 });
	assert.equal(bad.status, 400);
	const badWeight = await first
		.post("/api/inventory")
		.set("x-csrf-token", firstCsrf)
		.send({ origin: "Huila", initialWeightG: -5 });
	assert.equal(badWeight.status, 400);

	const created = await first
		.post("/api/inventory")
		.set("x-csrf-token", firstCsrf)
		.send({ origin: "Huila, Colombia", variety: "Caturra", initialWeightG: 2000 });
	assert.equal(created.status, 201);
	assert.equal(created.body.lot.remainingWeightG, 2000);

	assert.equal((await first.get("/api/inventory")).body.lots.length, 1);
	assert.equal((await second.get("/api/inventory")).body.lots.length, 0);
	assert.equal((await second.get(`/api/inventory/${created.body.lot.id}`)).status, 404);
});

test("inventory: consume decrements remaining, allows negative, is idempotent per plan, and logs", async () => {
	const { app } = await setup();
	const agent = request.agent(app);
	const { csrf } = await signup(agent, "roaster@example.com");
	const planId = await bootstrapPlan(agent, csrf);
	const lot = (
		await agent
			.post("/api/inventory")
			.set("x-csrf-token", csrf)
			.send({ origin: "Huila", initialWeightG: 300 })
	).body.lot;

	const first = await agent
		.post(`/api/inventory/${lot.id}/consume`)
		.set("x-csrf-token", csrf)
		.send({ weightG: 250, roastPlanId: planId });
	assert.equal(first.status, 201);
	assert.equal(first.body.lot.remainingWeightG, 50);

	// A second draw against the SAME plan is rejected — one draw-down per roast plan, ever.
	const dupe = await agent
		.post(`/api/inventory/${lot.id}/consume`)
		.set("x-csrf-token", csrf)
		.send({ weightG: 10, roastPlanId: planId });
	assert.equal(dupe.status, 409);
	assert.equal(dupe.body.code, "already_consumed");
	assert.equal(
		(await agent.get(`/api/inventory/${lot.id}`)).body.lot.remainingWeightG,
		50,
		"remaining must be unchanged after the rejected duplicate",
	);

	// A manual (plan-less) draw is unlimited and can push remaining negative — an honest
	// signal of paperwork/shelf drift, not clamped.
	const manual = await agent
		.post(`/api/inventory/${lot.id}/consume`)
		.set("x-csrf-token", csrf)
		.send({ weightG: 100 });
	assert.equal(manual.status, 201);
	assert.equal(manual.body.lot.remainingWeightG, -50);

	const zero = await agent
		.post(`/api/inventory/${lot.id}/consume`)
		.set("x-csrf-token", csrf)
		.send({ weightG: 0 });
	assert.equal(zero.status, 400);

	const withLog = await agent.get(`/api/inventory/${lot.id}`);
	assert.equal(withLog.body.log.length, 2);
	// Newest first: the manual (plan-less) draw has no plan title; the earlier draw does.
	assert.equal(withLog.body.log[0].roastPlanId, null);
	assert.equal(withLog.body.log[0].planTitle, null);
	assert.equal(withLog.body.log[1].roastPlanId, planId);
	assert.equal(withLog.body.log[1].planTitle, "Test coffee");
});

test("inventory: consuming against another user's plan is refused", async () => {
	const { app } = await setup();
	const owner = request.agent(app);
	const attacker = request.agent(app);
	const { csrf: ownerCsrf } = await signup(owner, "owner@example.com");
	const { csrf: attackerCsrf } = await signup(attacker, "attacker@example.com");
	const planId = await bootstrapPlan(owner, ownerCsrf);
	const lot = (
		await attacker
			.post("/api/inventory")
			.set("x-csrf-token", attackerCsrf)
			.send({ origin: "Huila", initialWeightG: 500 })
	).body.lot;
	const consume = await attacker
		.post(`/api/inventory/${lot.id}/consume`)
		.set("x-csrf-token", attackerCsrf)
		.send({ weightG: 50, roastPlanId: planId });
	assert.equal(consume.status, 404);
});

test("inventory: edit never accepts remainingWeightG directly, but shifting initialWeightG shifts remaining by the delta", async () => {
	const { app } = await setup();
	const agent = request.agent(app);
	const { csrf } = await signup(agent, "roaster@example.com");
	const lot = (
		await agent
			.post("/api/inventory")
			.set("x-csrf-token", csrf)
			.send({ origin: "Huila", initialWeightG: 1000 })
	).body.lot;
	await agent
		.post(`/api/inventory/${lot.id}/consume`)
		.set("x-csrf-token", csrf)
		.send({ weightG: 400 });

	// Attempting to set remainingWeightG directly is silently ignored.
	const sneaky = await agent
		.put(`/api/inventory/${lot.id}`)
		.set("x-csrf-token", csrf)
		.send({ remainingWeightG: 999999 });
	assert.equal(sneaky.body.lot.remainingWeightG, 600);

	// Correcting the recorded initial weight (e.g. a scale error) shifts remaining by the delta.
	const corrected = await agent
		.put(`/api/inventory/${lot.id}`)
		.set("x-csrf-token", csrf)
		.send({ initialWeightG: 1100 });
	assert.equal(corrected.body.lot.initialWeightG, 1100);
	assert.equal(corrected.body.lot.remainingWeightG, 700);

	const archived = await agent
		.put(`/api/inventory/${lot.id}`)
		.set("x-csrf-token", csrf)
		.send({ archived: true });
	assert.equal(archived.body.lot.archived, true);

	assert.equal(
		(await agent.delete(`/api/inventory/${lot.id}`).set("x-csrf-token", csrf)).status,
		200,
	);
	assert.equal((await agent.get(`/api/inventory/${lot.id}`)).status, 404);
});
